├── vendor └── .gitkeep ├── tests ├── unit │ └── .gitkeep ├── dummy │ ├── public │ │ ├── .gitkeep │ │ ├── robots.txt │ │ └── crossdomain.xml │ ├── app │ │ ├── models │ │ │ └── .gitkeep │ │ ├── styles │ │ │ ├── .gitkeep │ │ │ └── app.css │ │ ├── templates │ │ │ └── application.hbs │ │ ├── pods │ │ │ └── application │ │ │ │ └── template.hbs │ │ ├── router.js │ │ ├── app.js │ │ └── index.html │ ├── .jshintrc │ └── config │ │ └── environment.js ├── test-helper.js ├── helpers │ ├── resolver.js │ └── start-app.js ├── index.html └── .jshintrc ├── .watchmanconfig ├── node-tests ├── unit │ ├── jshint-test.js │ ├── index-test.js │ ├── tasks │ │ ├── deploy-test.js │ │ ├── read-config-test.js │ │ └── pipeline-test.js │ └── models │ │ └── pipeline-test.js ├── fixtures │ ├── .env │ ├── .env.deploy.development │ ├── config │ │ ├── deploy-for-addons-config-test-with-alias.js │ │ ├── deploy-for-addons-config-test-with-aliases.js │ │ ├── deploy-postbuild.js │ │ ├── deploy.js │ │ └── deploy.json │ └── config-with-defaults │ │ └── deploy.js ├── helpers │ ├── expect.js │ └── fake-progress-bar.js └── .jshintrc ├── .jshintignore ├── .bowerrc ├── blueprints ├── .jshintrc └── ember-cli-deploy │ ├── index.js │ └── files │ └── config │ └── deploy.js ├── config └── environment.js ├── lib ├── commands │ ├── index.js │ ├── activate.js │ ├── list.js │ └── deploy.js ├── helpers │ └── option-value.js ├── tasks │ ├── deploy.js │ ├── read-config.js │ └── pipeline.js └── models │ └── pipeline.js ├── .npmignore ├── testem.json ├── .ember-cli ├── CODE_OF_CONDUCT.md ├── .gitignore ├── .travis.yml ├── bower.json ├── ember-cli-build.js ├── .jshintrc ├── .editorconfig ├── LICENSE.md ├── README.md ├── index.js ├── package.json ├── bin └── changelog ├── UPGRADE_TO_0.5.x.md ├── MIGRATING_FROM_0_0_x_TO_0_4_x.md ├── rfc └── plugins.md └── CHANGELOG.md /vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp"] 3 | } 4 | -------------------------------------------------------------------------------- /node-tests/unit/jshint-test.js: -------------------------------------------------------------------------------- 1 | require('mocha-jshint')(); 2 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /tmp 3 | /node_modules 4 | /bower_components 5 | 6 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /node-tests/fixtures/.env: -------------------------------------------------------------------------------- 1 | OVERRIDDEN='main-dot-env-flavor' 2 | SHARED='shared-key' 3 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /node-tests/fixtures/.env.deploy.development: -------------------------------------------------------------------------------- 1 | ENVTEST=SUCCESS 2 | OVERRIDDEN='deploy-env-flavor' 3 | -------------------------------------------------------------------------------- /blueprints/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "console" 4 | ], 5 | "strict": false 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember.js

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/pods/application/template.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember.js

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment */) { 4 | return { something: 'test' }; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /lib/commands/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'deploy': require('./deploy'), 3 | 'deploy:activate': require('./activate'), 4 | 'deploy:list': require('./list'), 5 | }; 6 | -------------------------------------------------------------------------------- /node-tests/fixtures/config/deploy-for-addons-config-test-with-alias.js: -------------------------------------------------------------------------------- 1 | module.exports = function(/* environment */) { 2 | return { 3 | plugins: ['foo-plugin:bar-alias'] 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /node-tests/helpers/expect.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var chaiAsPromised = require('chai-as-promised'); 3 | 4 | chai.use(chaiAsPromised); 5 | 6 | module.exports = chai.expect; 7 | -------------------------------------------------------------------------------- /node-tests/fixtures/config/deploy-for-addons-config-test-with-aliases.js: -------------------------------------------------------------------------------- 1 | module.exports = function(/* environment */) { 2 | return { 3 | plugins: ['foo-plugin', 'foo-plugin:bar-alias', 'foo-plugin:doo-alias'] 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | tmp/ 4 | dist/ 5 | 6 | .bowerrc 7 | .editorconfig 8 | .ember-cli 9 | .travis.yml 10 | .npmignore 11 | **/.gitkeep 12 | bower.json 13 | ember-cli-build.js 14 | Brocfile.js 15 | testem.json 16 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "disable_watching": true, 5 | "launch_in_ci": [ 6 | "PhantomJS" 7 | ], 8 | "launch_in_dev": [ 9 | "PhantomJS", 10 | "Chrome" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | }); 10 | 11 | export default Router; 12 | -------------------------------------------------------------------------------- /node-tests/fixtures/config/deploy-postbuild.js: -------------------------------------------------------------------------------- 1 | module.exports = function(environment) { 2 | var ENV = {}; 3 | 4 | if (environment === 'development-postbuild') { 5 | ENV.pipeline = { 6 | activateOnDeploy: true 7 | }; 8 | } 9 | 10 | return ENV; 11 | }; 12 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /node-tests/helpers/fake-progress-bar.js: -------------------------------------------------------------------------------- 1 | function FakeProgressBar(template, options) { 2 | this.template = template; 3 | this.total = options.total; 4 | this.ticks = []; 5 | } 6 | 7 | FakeProgressBar.prototype.tick = function(options) { 8 | this.ticks.push(options); 9 | }; 10 | 11 | module.exports = FakeProgressBar; 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | The Ember team and community are committed to everyone having a safe and inclusive experience. 2 | 3 | **Our Community Guidelines / Code of Conduct can be found here**: 4 | 5 | http://emberjs.com/guidelines/ 6 | 7 | For a history of updates, see the page history here: 8 | 9 | https://github.com/emberjs/website/commits/master/source/guidelines.html.erb 10 | -------------------------------------------------------------------------------- /lib/helpers/option-value.js: -------------------------------------------------------------------------------- 1 | module.exports = function(commandOption, settings, optionKey, defaultValue) { 2 | if (commandOption !== undefined) { 3 | return commandOption; 4 | } 5 | if (settings && settings['ember-cli-deploy'] && settings['ember-cli-deploy'][optionKey] !== undefined) { 6 | return settings['ember-cli-deploy'][optionKey]; 7 | } 8 | return defaultValue; 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 | deploy_test.json 19 | .DS_Store 20 | NERD_tree_2 21 | *.tgz 22 | /tmp-delete-excluded-test 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | before_install: 13 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 14 | - "npm config set spin false" 15 | - "npm install -g npm@^2" 16 | 17 | install: 18 | - npm install -g bower 19 | - npm install 20 | - bower install 21 | 22 | script: 23 | - npm test 24 | -------------------------------------------------------------------------------- /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 | var App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver: Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-deploy", 3 | "dependencies": { 4 | "ember": "1.13.8", 5 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", 6 | "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", 7 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", 8 | "ember-qunit": "0.4.9", 9 | "ember-qunit-notifications": "0.0.7", 10 | "ember-resolver": "~0.1.18", 11 | "jquery": "^1.11.3", 12 | "loader.js": "ember-cli/loader.js#3.2.1", 13 | "qunit": "~1.18.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | var EmberApp = require('ember-cli/lib/broccoli/ember-addon'); 3 | 4 | module.exports = function(defaults) { 5 | var app = new EmberApp(defaults, { 6 | // Add options here 7 | }); 8 | 9 | /* 10 | This build file specifes the options for the dummy test app of this 11 | addon, located in `/tests/dummy` 12 | This build file does *not* influence how the addon or the app using it 13 | behave. You most likely want to be modifying `./index.js` or app's build file 14 | */ 15 | 16 | return app.toTree(); 17 | }; 18 | -------------------------------------------------------------------------------- /node-tests/fixtures/config/deploy.js: -------------------------------------------------------------------------------- 1 | module.exports = function(environment) { 2 | var ENV = {}; 3 | 4 | if (environment === 'development') { 5 | ENV.build = { 6 | environment: 'development' 7 | }; 8 | 9 | ENV.s3 = { 10 | bucket: 'shineonyoucrazy', 11 | region: 'us-east-1' 12 | }; 13 | } 14 | 15 | if (environment === 'staging') { 16 | ENV.build = { 17 | environment: 'production' 18 | }; 19 | 20 | ENV.s3 = { 21 | bucket: 'keepitreal', 22 | region: 'us-east-1' 23 | }; 24 | } 25 | 26 | return ENV; 27 | }; 28 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | var application; 7 | 8 | var attributes = Ember.merge({}, config.APP); 9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 10 | 11 | Ember.run(function() { 12 | application = Application.create(attributes); 13 | application.setupForTesting(); 14 | application.injectTestHelpers(); 15 | }); 16 | 17 | return application; 18 | } 19 | -------------------------------------------------------------------------------- /blueprints/ember-cli-deploy/index.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | var green = chalk.green; 3 | 4 | module.exports = { 5 | description: 'Generate config for ember-cli deployments', 6 | normalizeEntityName: function() { 7 | // this prevents an error when the entityName is 8 | // not specified (since that doesn't actually matter 9 | // to us 10 | }, 11 | afterInstall: function(options) { 12 | this.ui.write(green('ember-cli-deploy needs plugins to actually do the deployment work.\nSee http://ember-cli.github.io/ember-cli-deploy/docs/v0.5.x/quick-start/\nto learn how to install plugins and see what plugins are available.\n')); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | "node": true 33 | } 34 | -------------------------------------------------------------------------------- /.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 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /node-tests/fixtures/config/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "store": { 4 | "host": "localhost", 5 | "port": 6379 6 | }, 7 | "assets": { 8 | "accessKeyId": "", 9 | "secretAccessKey": "", 10 | "bucket": "" 11 | } 12 | }, 13 | 14 | "staging": { 15 | "store": { 16 | "host": "staging-redis.firstiwaslike.com", 17 | "port": 6379 18 | }, 19 | "assets": { 20 | "accessKeyId": "", 21 | "secretAccessKey": "", 22 | "bucket": "", 23 | "prefix": "" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ember CLI Deploy 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | {{content-for 'head-footer'}} 16 | 17 | 18 | {{content-for 'body'}} 19 | 20 | 21 | 22 | 23 | {{content-for 'body-footer'}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /node-tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise", 6 | "describe", 7 | "beforeEach", 8 | "afterEach", 9 | "it" 10 | ], 11 | "browser": true, 12 | "boss": true, 13 | "curly": true, 14 | "debug": false, 15 | "devel": true, 16 | "eqeqeq": true, 17 | "evil": true, 18 | "forin": false, 19 | "immed": false, 20 | "laxbreak": false, 21 | "newcap": true, 22 | "noarg": true, 23 | "noempty": false, 24 | "nonew": false, 25 | "nomen": false, 26 | "onevar": false, 27 | "plusplus": false, 28 | "regexp": false, 29 | "undef": true, 30 | "sub": true, 31 | "strict": false, 32 | "white": false, 33 | "eqnull": true, 34 | "esnext": true, 35 | "unused": true, 36 | "node": true, 37 | "expr": true 38 | } 39 | -------------------------------------------------------------------------------- /blueprints/ember-cli-deploy/files/config/deploy.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(deployTarget) { 4 | var ENV = { 5 | build: {} 6 | // include other plugin configuration that applies to all deploy targets here 7 | }; 8 | 9 | if (deployTarget === 'development') { 10 | ENV.build.environment = 'development'; 11 | // configure other plugins for development deploy target here 12 | } 13 | 14 | if (deployTarget === 'staging') { 15 | ENV.build.environment = 'production'; 16 | // configure other plugins for staging deploy target here 17 | } 18 | 19 | if (deployTarget === 'production') { 20 | ENV.build.environment = 'production'; 21 | // configure other plugins for production deploy target here 22 | } 23 | 24 | // Note: if you need to build some configuration asynchronously, you can return 25 | // a promise that resolves with the ENV object instead of returning the 26 | // ENV object synchronously. 27 | return ENV; 28 | }; 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember CLI Deploy 2 | [![Build Status](https://travis-ci.org/ember-cli/ember-cli-deploy.svg?branch=master)](https://travis-ci.org/ember-cli/ember-cli-deploy) [![Code Climate](https://codeclimate.com/github/ember-cli/ember-cli-deploy/badges/gpa.svg)](https://codeclimate.com/github/ember-cli/ember-cli-deploy) 3 | 4 | Simple, flexible deployment for your Ember CLI app 5 | 6 | ## Installation 7 | 8 | ``` 9 | ember install ember-cli-deploy 10 | ``` 11 | ## Quick start 12 | 13 | After installation, choose [plugins](http://ember-cli.github.io/ember-cli-deploy/docs/v0.5.x/plugins/) matching your deployment environment, [configure](http://ember-cli.github.io/ember-cli-deploy/docs/v0.5.x/configuration-overview/) your deployment script appropriately and you're ready to [start deploying](http://ember-cli.github.io/ember-cli-deploy/docs/v0.5.x/usage-overview/). 14 | 15 | ## In-depth documentation 16 | 17 | [Visit the Docs site](http://ember-cli.github.io/ember-cli-deploy/) 18 | 19 | ## Contributing 20 | 21 | Clone the repo and run `npm install`. To run tests, 22 | 23 | npm test 24 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dummy', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | } 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | ENV.baseURL = '/'; 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | } 41 | 42 | if (environment === 'production') { 43 | 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /node-tests/unit/index-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('ember-cli/tests/helpers/assert'); 2 | 3 | describe ('ember-cli-deploy', function() { 4 | var subject; 5 | beforeEach(function() { 6 | subject = require('../../index.js'); 7 | }); 8 | describe('postBuild', function() { 9 | it('requires an app', function() { 10 | assert(!subject.postBuild(), 'returns false'); 11 | }); 12 | 13 | it('requires a deployTarget', function() { 14 | var context = { 15 | app: { 16 | options: { 17 | emberCLIDeploy: { 18 | runOnPostBuild: null 19 | } 20 | } 21 | } 22 | }; 23 | 24 | assert(!subject.postBuild.apply(context)); 25 | }); 26 | 27 | it('reads the config', function() { 28 | var context = { 29 | project: { 30 | name: function() {return 'test-project';}, 31 | root: process.cwd(), 32 | addons: [] 33 | }, 34 | app: { 35 | options: { 36 | emberCLIDeploy: { 37 | runOnPostBuild: 'production', 38 | configFile: 'node-tests/fixtures/config/deploy.js' 39 | } 40 | } 41 | } 42 | }; 43 | 44 | assert(subject.postBuild.apply(context)); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /node-tests/fixtures/config-with-defaults/deploy.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "development": { 3 | "build": { 4 | "environment": "development" 5 | }, 6 | "buildPath": "tmp/deploy-dist/", 7 | "indexFiles": null, 8 | "manifestPrefix": "foo", 9 | "tagging": "sha", 10 | "store": { 11 | "host": "localhost", 12 | "manifestSize": 10, 13 | "port": 6379, 14 | "type": "redis" 15 | }, 16 | "assets": { 17 | "accessKeyId": "", 18 | "secretAccessKey": "", 19 | "bucket": "", 20 | "exclude": [], 21 | "gzip": true, 22 | "gzipExtensions": ["js", "css", "svg"], 23 | "type": "s3" 24 | } 25 | }, 26 | 27 | "staging": { 28 | "build": { 29 | "environment": "production" 30 | }, 31 | "buildPath": "tmp/deploy-dist/", 32 | "indexFiles": null, 33 | "manifestPrefix": "foo", 34 | "tagging": "sha", 35 | "store": { 36 | "host": "staging-redis.firstiwaslike.com", 37 | "manifestSize": 10, 38 | "port": 6379, 39 | "type": "redis" 40 | }, 41 | "assets": { 42 | "accessKeyId": "", 43 | "secretAccessKey": "", 44 | "bucket": "", 45 | "exclude": [], 46 | "gzip": true, 47 | "prefix": "", 48 | "gzipExtensions": ["js", "css", "svg"], 49 | "type": "s3" 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var commands = require('./lib/commands'); 3 | 4 | module.exports = { 5 | name: 'ember-cli-deploy', 6 | 7 | includedCommands: function() { 8 | return commands; 9 | }, 10 | 11 | blueprintsPath: function() { 12 | return path.join(__dirname, 'blueprints'); 13 | }, 14 | 15 | postBuild: function(result) { 16 | var _this = this; 17 | if (!this.app) { 18 | // You will need ember-cli >= 1.13 to use ember-cli-deploy's postBuild integration. 19 | // This is because prior to 1.13, `this.app` is not available in the postBuild hook. 20 | return; 21 | } 22 | var options = this.app.options.emberCLIDeploy || {}; 23 | 24 | var deployTarget = options.runOnPostBuild; 25 | if (deployTarget) { 26 | var ReadConfigTask = require('./lib/tasks/read-config'); 27 | var readConfig = new ReadConfigTask({ 28 | project: this.project, 29 | deployTarget: deployTarget, 30 | deployConfigPath: options.configFile 31 | }); 32 | return readConfig.run().then(function(config){ 33 | var DeployTask = require('./lib/tasks/deploy'); 34 | var deploy = new DeployTask({ 35 | project: _this.project, 36 | ui: _this.ui, 37 | deployTarget: deployTarget, 38 | config: config, 39 | shouldActivate: options.shouldActivate, 40 | commandOptions: { 41 | buildDir: result.directory 42 | } 43 | }); 44 | return deploy.run(); 45 | }); 46 | } 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /lib/tasks/deploy.js: -------------------------------------------------------------------------------- 1 | var Task = require('ember-cli/lib/models/task'); 2 | var PipelineTask = require('../tasks/pipeline'); 3 | 4 | module.exports = Task.extend({ 5 | init: function() { 6 | this.commandOptions = this.commandOptions || {}; 7 | this.shouldActivate = this.shouldActivate || this._shouldActivate(this.commandOptions); 8 | }, 9 | 10 | run: function() { 11 | var pipeline = this._pipeline || new PipelineTask({ 12 | project: this.project, 13 | ui: this.ui, 14 | deployTarget: this.deployTarget, 15 | config: this.config, 16 | commandOptions: this.commandOptions, 17 | hooks: this._hooks(this.shouldActivate) 18 | }); 19 | return pipeline.run(); 20 | }, 21 | 22 | _shouldActivate: function(options) { 23 | var pipelineConfig = this.config['pipeline'] || {}; 24 | var shouldReferToPipelineConfig = (options.activate === undefined); 25 | return shouldReferToPipelineConfig ? pipelineConfig.activateOnDeploy : options.activate; 26 | }, 27 | 28 | _hooks: function(shouldActivate) { 29 | var hooks = ['configure', 30 | 'setup', 31 | 'willDeploy', 32 | 'willBuild', 'build', 'didBuild', 33 | 'willPrepare', 'prepare', 'didPrepare', 34 | 'fetchInitialRevisions', 35 | 'willUpload', 'upload', 'didUpload']; 36 | 37 | if (shouldActivate) { 38 | hooks.push('willActivate', 'activate', 'fetchRevisions', 'didActivate'); 39 | } else { 40 | hooks.push('fetchRevisions'); 41 | } 42 | 43 | hooks.push('didDeploy', 'teardown'); 44 | 45 | return hooks; 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /lib/commands/activate.js: -------------------------------------------------------------------------------- 1 | var chooseOptionValue = require('../helpers/option-value'); 2 | 3 | module.exports = { 4 | name: 'deploy:activate', 5 | description: 'Activates a passed deploy-revision', 6 | works: 'insideProject', 7 | 8 | availableOptions: [ 9 | { name: 'revision', type: String, required: true }, 10 | { name: 'verbose', type: Boolean }, 11 | { name: 'deploy-config-file', type: String, description: '(Default: config/deploy.js)' } 12 | ], 13 | 14 | anonymousOptions: [ 15 | '' 16 | ], 17 | 18 | run: function(commandOptions, rawArgs) { 19 | commandOptions.deployTarget = rawArgs.shift(); 20 | 21 | this.ui.verbose = chooseOptionValue(commandOptions.verbose, this.settings, 'verbose'); 22 | commandOptions.deployConfigFile = chooseOptionValue(commandOptions.deployConfigFile, this.settings, 'deploy-config-file', 'config/deploy.js'); 23 | 24 | process.env.DEPLOY_TARGET = commandOptions.deployTarget; 25 | 26 | var ReadConfigTask = require('../tasks/read-config'); 27 | var readConfig = new ReadConfigTask({ 28 | project: this.project, 29 | deployTarget: commandOptions.deployTarget, 30 | deployConfigFile: commandOptions.deployConfigFile 31 | }); 32 | var self = this; 33 | return readConfig.run().then(function(config){ 34 | var PipelineTask = require('../tasks/pipeline'); 35 | var pipeline = new PipelineTask({ 36 | project: self.project, 37 | ui: self.ui, 38 | config: config, 39 | deployTarget: commandOptions.deployTarget, 40 | commandOptions: commandOptions, 41 | hooks: [ 42 | 'configure', 43 | 'setup', 44 | 'fetchInitialRevisions', 45 | 'willActivate', 46 | 'activate', 47 | 'fetchRevisions', 48 | 'didActivate', 49 | 'teardown' 50 | ] 51 | }); 52 | 53 | return pipeline.run(); 54 | }); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-deploy", 3 | "description": "A deployment pipeline for ember-cli apps", 4 | "version": "0.6.0", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "start": "ember server", 11 | "build": "ember build", 12 | "test": "node node_modules/mocha/bin/mocha 'node-tests/**/*-test.js'", 13 | "autotest": "node node_modules/mocha/bin/mocha --watch --reporter spec 'node-tests/**/*-test.js'" 14 | }, 15 | "repository": "https://github.com/ember-cli/ember-cli-deploy", 16 | "engines": { 17 | "node": ">= 0.10.0" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "chai": "^1.9.2", 23 | "chai-as-promised": "^4.1.1", 24 | "broccoli-asset-rev": "^2.1.2", 25 | "ember-cli": "1.13.8", 26 | "ember-cli-app-version": "0.5.0", 27 | "ember-cli-content-security-policy": "0.4.0", 28 | "ember-cli-dependency-checker": "^1.0.1", 29 | "ember-cli-htmlbars": "0.7.9", 30 | "ember-cli-htmlbars-inline-precompile": "^0.2.0", 31 | "ember-cli-ic-ajax": "0.2.1", 32 | "ember-cli-inject-live-reload": "^1.3.1", 33 | "ember-cli-qunit": "^1.0.0", 34 | "github": "0.2.3", 35 | "mocha": "^2.0.1", 36 | "mocha-jshint": "^2.2.6", 37 | "sinon": "^1.12.1" 38 | }, 39 | "keywords": [ 40 | "ember-addon", 41 | "ember-cli-deploy" 42 | ], 43 | "ember-addon": { 44 | "configPath": "tests/dummy/config" 45 | }, 46 | "dependencies": { 47 | "broccoli": "^0.13.2", 48 | "broccoli-gzip": "^0.2.0", 49 | "chalk": "^0.5.1", 50 | "core-object": "0.0.2", 51 | "ember-cli-babel": "^5.1.3", 52 | "dotenv": "^1.1.0", 53 | "glob": "4.4.2", 54 | "lodash-node": "^2.4.1", 55 | "mkdirp": "^0.5.0", 56 | "ncp": "^2.0.0", 57 | "rimraf": "^2.2.8", 58 | "silent-error": "^1.0.0", 59 | "ember-cli-deploy-progress": "^1.3.0", 60 | "sync-exec": "^0.5.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/commands/list.js: -------------------------------------------------------------------------------- 1 | var chooseOptionValue = require('../helpers/option-value'); 2 | 3 | module.exports = { 4 | name: 'deploy:list', 5 | description: 'Lists the currently uploaded deploy-revisions', 6 | works: 'insideProject', 7 | 8 | anonymousOptions: [ 9 | '' 10 | ], 11 | 12 | availableOptions: [ 13 | { name: 'deploy-config-file', type: String, description: '(Default: config/deploy.js)' }, 14 | { name: 'verbose', type: Boolean }, 15 | { name: 'amount', type: Number, description: '(Default: 10)' } 16 | ], 17 | 18 | run: function(commandOptions, rawArgs) { 19 | commandOptions.deployTarget = rawArgs.shift(); 20 | 21 | commandOptions.deployConfigFile = chooseOptionValue(commandOptions.deployConfigFile, this.settings, 'deploy-config-file', 'config/deploy.js'); 22 | this.ui.verbose = chooseOptionValue(commandOptions.verbose, this.settings, 'verbose'); 23 | commandOptions.amount = chooseOptionValue(commandOptions.amount, this.settings, 'amount', 10); 24 | 25 | process.env.DEPLOY_TARGET = commandOptions.deployTarget; 26 | 27 | var ReadConfigTask = require('../tasks/read-config'); 28 | var readConfig = new ReadConfigTask({ 29 | project: this.project, 30 | deployTarget: commandOptions.deployTarget, 31 | deployConfigFile: commandOptions.deployConfigFile 32 | }); 33 | var self = this; 34 | return readConfig.run().then(function(config){ 35 | var PipelineTask = require('../tasks/pipeline'); 36 | var pipeline = new PipelineTask({ 37 | project: self.project, 38 | ui: self.ui, 39 | config: config, 40 | deployTarget: commandOptions.deployTarget, 41 | commandOptions: commandOptions, 42 | hooks: [ 43 | 'configure', 44 | 'setup', 45 | 'fetchRevisions', 46 | 'displayRevisions', 47 | 'teardown' 48 | ] 49 | }); 50 | 51 | return pipeline.run(); 52 | }); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /lib/tasks/read-config.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 | 5 | var existsSync = require('fs').existsSync; 6 | var path = require('path'); 7 | var dotenv = require('dotenv'); 8 | 9 | module.exports = Task.extend({ 10 | init: function() { 11 | if (!this.project) { 12 | throw new SilentError('No project passed to read-config task'); 13 | } 14 | 15 | if(!this.deployTarget) { 16 | throw new SilentError('No deployTarget passed to read-config task'); 17 | } 18 | 19 | this.root = this.project.root; 20 | 21 | this.deployConfigPath = this.deployConfigPath || 'config/deploy.js'; 22 | 23 | if (!existsSync(path.join(this.root, this.deployConfigPath))) { 24 | throw new SilentError('Deploy config does not exist at `' + this.deployConfigPath + '`'); 25 | } 26 | }, 27 | 28 | run: function() { 29 | this._loadDotEnv(); 30 | return this._readDeployConfig(); 31 | }, 32 | 33 | _loadDotEnv: function() { 34 | var root = this.root; 35 | 36 | var deployDotEnvFilename = '.env.deploy.' + this.deployTarget; 37 | var deployDotEnvFilePath = path.join(root, deployDotEnvFilename); 38 | 39 | var dotEnvFilename = '.env'; 40 | var dotEnvFilePath = path.join(root, dotEnvFilename); 41 | 42 | // order is important here. vars defined in files loaded first 43 | // will override files loaded after. 44 | var paths = [deployDotEnvFilePath, dotEnvFilePath]; 45 | paths.forEach(function(path) { 46 | if (existsSync(path)) { 47 | dotenv.load({ 48 | path: path 49 | }); 50 | } 51 | }); 52 | }, 53 | 54 | _readDeployConfig: function() { 55 | var root = this.root; 56 | var deployConfigFn = require(path.resolve(root, this.deployConfigPath)); 57 | return Promise.resolve(deployConfigFn(this.deployTarget)); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /lib/commands/deploy.js: -------------------------------------------------------------------------------- 1 | var chooseOptionValue = require('../helpers/option-value'); 2 | 3 | module.exports = { 4 | name: 'deploy', 5 | description: 'Deploys an ember-cli app', 6 | works: 'insideProject', 7 | 8 | anonymousOptions: [ 9 | '' 10 | ], 11 | 12 | // note: we can not use `default` from ember-cli because we need to use 13 | // settings from .ember-cli config-file as secondary defaults 14 | availableOptions: [ 15 | { name: 'deploy-config-file', type: String, description: '(Default: config/deploy.js)' }, 16 | { name: 'verbose', type: Boolean, description: '(Default: false)' }, 17 | { name: 'activate', type: Boolean, description: '(Default: false)' }, 18 | { name: 'show-progress', type: Boolean, aliases: ['p', 'progress'], description: '(Default: true)'} 19 | ], 20 | 21 | run: function(commandOptions, rawArgs) { 22 | commandOptions.deployTarget = rawArgs.shift(); 23 | 24 | commandOptions.deployConfigFile = chooseOptionValue(commandOptions.deployConfigFile, this.settings, 'deploy-config-file', 'config/deploy.js'); 25 | commandOptions.activate = chooseOptionValue(commandOptions.activate, this.settings, 'activate'); 26 | 27 | this.ui.verbose = chooseOptionValue(commandOptions.verbose, this.settings, 'verbose'); 28 | this.ui.showProgress = chooseOptionValue(commandOptions.showProgress, this.settings, 'showProgress', process.stdout.isTTY ? true : false); 29 | 30 | process.env.DEPLOY_TARGET = commandOptions.deployTarget; 31 | 32 | var ReadConfigTask = require('../tasks/read-config'); 33 | var readConfig = new ReadConfigTask({ 34 | project: this.project, 35 | deployTarget: commandOptions.deployTarget, 36 | deployConfigFile: commandOptions.deployConfigFile 37 | }); 38 | var self = this; 39 | return readConfig.run().then(function(config){ 40 | var DeployTask = require('../tasks/deploy'); 41 | var deploy = new DeployTask({ 42 | project: self.project, 43 | ui: self.ui, 44 | config: config, 45 | deployTarget: commandOptions.deployTarget, 46 | commandOptions: commandOptions 47 | }); 48 | 49 | return deploy.run(); 50 | }); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /bin/changelog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | /* 5 | * This script generates the template a changelog by comparing a current version 6 | * with master. Run this, copy what's logged into the `CHANGELOG.md` and update 7 | * the top section based on the changes listed in "Community Contributions" 8 | * 9 | * Usage: 10 | * 11 | * bin/changelog 12 | */ 13 | 14 | var EOL = require('os').EOL; 15 | var Promise = require('ember-cli/lib/ext/promise'); 16 | var GitHubApi = require('github'); 17 | 18 | var github = new GitHubApi({version: '3.0.0'}); 19 | var compareCommits = Promise.denodeify(github.repos.compareCommits); 20 | var currentVersion = 'v' + require('../package').version; 21 | 22 | compareCommits({ 23 | user: 'ember-cli', 24 | repo: 'ember-cli-deploy', 25 | base: currentVersion, 26 | head: 'master' 27 | }).then(function(res) { 28 | return res.commits.map(function(commitInfo) { 29 | return commitInfo.commit.message 30 | 31 | }).filter(function(message) { 32 | return message.indexOf('Merge pull request #') > -1; 33 | 34 | }).map(function(message) { 35 | var numAndAuthor = message.match(/#(\d+) from (.*)\//).slice(1,3); 36 | var title = message.split('\n\n')[1]; 37 | 38 | return { 39 | number: +numAndAuthor[0], 40 | author: numAndAuthor[1], 41 | title: title 42 | }; 43 | 44 | }).sort(function(a, b) { 45 | return a.number > b.number; 46 | }).map(function(pr) { 47 | var link = '[#' + pr.number + ']' + 48 | '(https://github.com/ember-cli/ember-cli-deploy/pull/' + pr.number + ')'; 49 | var title = pr.title; 50 | var author = '[@' + pr.author + ']' + 51 | '(https://github.com/' + pr.author +')'; 52 | 53 | return '- ' + link + ' ' + title + ' ' + author; 54 | 55 | }).join('\n'); 56 | 57 | }).then(function(contributions) { 58 | var changelog = generateChangelog(contributions); 59 | 60 | console.log(changelog); 61 | }).catch(function(err) { 62 | console.error(err); 63 | }) 64 | 65 | function generateChangelog(contributions) { 66 | var header = '#### Community Contributions'; 67 | var footer = 'Thank you to all who took the time to contribute!'; 68 | 69 | return header + EOL + EOL + contributions + EOL + EOL + footer; 70 | } 71 | -------------------------------------------------------------------------------- /UPGRADE_TO_0.5.x.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | The pipeline approach in 0.5.x requires some modifications to ember-cli-deploy plugins and a few to deploy configuration scripts. 4 | 5 | ## Plugin users 6 | 7 | - Add the following dependencies: `ember-cli-deploy-build` and `ember-cli-deploy-revision-data` 8 | - Change your config/deploy.js to return a function instead of an object, for each `environment` setting: 9 | - `ENV["%INDEX_ADAPTER_NAME%"] = { /* plugin config goes here */ }` 10 | - `ENV["%ASSETS_ADAPTER_NAME%"] = { /* plugin config goes here */ }` 11 | 12 | ## Plugin authors 13 | Depending on what type of adapters you're publishing, you'll have to make different amendments. 14 | 15 | ### For all adapters 16 | 17 | 1. update package.json keywords to include the keyword "ember-cli-deploy-plugin" 18 | 2. `npm install ember-cli-deploy-plugin --save` and subclass the new https://github.com/lukemelia/ember-cli-deploy-plugin for all your adapters. 19 | 3. implement `createDeployPlugin` in index.js and return an extended `ember-cli-deploy-plugin` from there. 20 | 4. listen to hooks to actually upload stuff, most notably upload (index and assets adapter) and activate (index adapter) 21 | 5. instruct your users to update their config (see above) 22 | 6. instruct your users to install ember-cli-deploy-build (see above) 23 | 7. replace console.log statements to this.log to play nicely with the formatting in ember-cli-deploy 24 | 25 | ### For index/store adapters: 26 | 1. instruct your users to install ember-cli-deploy-revision-data (see above) 27 | 2. mind you that the revision key is now under context.revisionData.revisionKey (provided ember-cli-deploy-revision-data is installed) and it doesn't include your project name yet. 28 | ```javascript 29 | _key: function(context) { 30 | var revisionKey = context.commandOptions.revision || context.revisionData.revisionKey.substr(0, 8); 31 | return context.project.name() + ':' + revisionKey; 32 | } 33 | ``` 34 | 3. mind you that you won't be passed the contents of index.html to your upload function, instead you will have to read the file using the filesystem package, 35 | ```javascript 36 | var path = require('path'); 37 | var fs = require('fs'); 38 | var readFile = denodeify(fs.readFile); 39 | 40 | upload: function() { 41 | readFile(path.join(context.distDir, "index.html")) 42 | .then(function(buffer) { 43 | return buffer.toString(); 44 | }).then(function(indexContents) { 45 | // do uploady stuff with contents here 46 | }); 47 | } 48 | ``` 49 | 50 | ### For asset adapters: 51 | 1. if you have a hard reference to "tmp/asset-sync", replace that with context.distDir. 52 | -------------------------------------------------------------------------------- /node-tests/unit/tasks/deploy-test.js: -------------------------------------------------------------------------------- 1 | var Promise = require('ember-cli/lib/ext/promise'); 2 | var DeployTask = require('../../../lib/tasks/deploy'); 3 | var expect = require('../../helpers/expect'); 4 | 5 | describe('DeployTask', function() { 6 | var mockProject = {addons: []}; 7 | var mockPostBuildConfig = { 8 | pipeline: { 9 | activateOnDeploy: true 10 | } 11 | }; 12 | var mockDeployConfig = { 13 | build: { 14 | buildEnv: 'development' 15 | } 16 | }; 17 | var mockUi = { write: function() {}, writeError: function() {} }; 18 | 19 | describe('creating and setting up a new instance', function() { 20 | 21 | describe('detects that shouldActivate', function() { 22 | it('is passed as a value', function() { 23 | var deploy = new DeployTask({ 24 | project: mockProject, 25 | ui: mockUi, 26 | deployTarget: 'development-postbuild', 27 | config: mockPostBuildConfig, 28 | shouldActivate: true 29 | }); 30 | expect(deploy.shouldActivate).to.eq(true); 31 | }); 32 | 33 | it('is specified in the deploy config', function() { 34 | var project = { 35 | name: function() {return 'test-project';}, 36 | root: process.cwd(), 37 | addons: [] 38 | }; 39 | var deploy = new DeployTask({ 40 | project: project, 41 | ui: mockUi, 42 | deployTarget: 'development-postbuild', 43 | config: mockPostBuildConfig, 44 | }); 45 | expect(deploy.shouldActivate).to.eq(true); 46 | }); 47 | 48 | it('is passed as a commandLine option', function() { 49 | var project = { 50 | name: function() {return 'test-project';}, 51 | root: process.cwd(), 52 | addons: [] 53 | }; 54 | var deploy = new DeployTask({ 55 | project: project, 56 | ui: mockUi, 57 | deployTarget: 'development-postbuild', 58 | config: mockDeployConfig, 59 | commandOptions: { 60 | activate: true 61 | } 62 | }); 63 | expect(deploy.shouldActivate).to.eq(true); 64 | }); 65 | }); 66 | 67 | describe('executing the deployTask', function() { 68 | it ('executes the pipelineTask', function() { 69 | var pipelineExecuted = false; 70 | 71 | var project = { 72 | name: function() {return 'test-project';}, 73 | root: process.cwd(), 74 | addons: [ ] 75 | }; 76 | 77 | var task = new DeployTask({ 78 | project: project, 79 | ui: mockUi, 80 | deployTarget: 'development', 81 | config: mockDeployConfig, 82 | _pipeline: { 83 | run: function() { 84 | pipelineExecuted = true; 85 | return Promise.resolve(); 86 | } 87 | } 88 | }); 89 | 90 | return expect(task.run()).to.be.fulfilled 91 | .then(function() { 92 | expect(pipelineExecuted).to.eq(true); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('setting environment variables from .env', function() { 98 | beforeEach(function(){ 99 | delete process.env.ENVTEST; 100 | }); 101 | }); 102 | 103 | }); 104 | 105 | }); 106 | -------------------------------------------------------------------------------- /node-tests/unit/tasks/read-config-test.js: -------------------------------------------------------------------------------- 1 | var ReadConfigTask = require('../../../lib/tasks/read-config'); 2 | var assert = require('chai').assert; 3 | var path = require('path'); 4 | 5 | describe('ReadConfigTask', function() { 6 | describe('#run', function() { 7 | it('reads from the config file', function(done){ 8 | var project = { 9 | name: function() {return 'test-project';}, 10 | root: process.cwd(), 11 | addons: [] 12 | }; 13 | 14 | var task = new ReadConfigTask({ 15 | project: project, 16 | deployTarget: 'development', 17 | deployConfigPath: 'node-tests/fixtures/config/deploy.js' 18 | }); 19 | task.run().then(function(config){ 20 | assert.equal(config.build.environment, 'development'); 21 | assert.equal(config.s3.bucket, 'shineonyoucrazy'); 22 | done(); 23 | }); 24 | }); 25 | 26 | it('accepts an absolute deployConfigPath', function() { 27 | var project = { 28 | name: function() {return 'test-project';}, 29 | root: process.cwd(), 30 | addons: [] 31 | }; 32 | 33 | var fn = function () { 34 | new ReadConfigTask({ 35 | project: project, 36 | deployTarget: 'development', 37 | deployConfigPath: path.join(process.cwd(), 'node-tests/fixtures/config/deploy.js') 38 | }).run(); 39 | }; 40 | 41 | assert.doesNotThrow(fn, /Cannot find module/, 'config file could not be read'); 42 | }); 43 | 44 | describe('setting environment variables from .env', function() { 45 | var project; 46 | var customDotEnvVars = [ 47 | 'OVERRIDDEN', 48 | 'SHARED', 49 | 'ENVTEST' 50 | ]; 51 | 52 | beforeEach(function(){ 53 | customDotEnvVars.forEach(function(dotEnvVar) { 54 | delete process.env[dotEnvVar]; 55 | }); 56 | 57 | project = { 58 | name: function() {return 'test-project';}, 59 | root: path.join(process.cwd(), 'node-tests/fixtures'), 60 | addons: [] 61 | }; 62 | }); 63 | it('sets the process.env vars if a .env file exists for deploy environment', function() { 64 | assert.isUndefined(process.env.ENVTEST); 65 | 66 | var task = new ReadConfigTask({ 67 | project: project, 68 | deployTarget: 'development', 69 | deployConfigPath: 'config/deploy.js' 70 | }); 71 | task.run(); 72 | 73 | assert.equal(process.env.ENVTEST, 'SUCCESS'); 74 | }); 75 | 76 | it('sets the process.env vars from main .env file', function() { 77 | assert.isUndefined(process.env.SHARED); 78 | 79 | var task = new ReadConfigTask({ 80 | project: project, 81 | deployTarget: 'development', 82 | deployConfigPath: 'config/deploy.js' 83 | }); 84 | task.run(); 85 | 86 | assert.equal(process.env.SHARED, 'shared-key'); 87 | }); 88 | 89 | it('overrides vars from main .env file if defined in deploy environment .env file', function() { 90 | assert.isUndefined(process.env.OVERRIDDEN); 91 | 92 | var task = new ReadConfigTask({ 93 | project: project, 94 | deployTarget: 'development', 95 | deployConfigPath: 'config/deploy.js' 96 | }); 97 | task.run(); 98 | 99 | assert.equal(process.env.OVERRIDDEN, 'deploy-env-flavor'); 100 | }); 101 | }); 102 | 103 | }); 104 | 105 | }); 106 | -------------------------------------------------------------------------------- /MIGRATING_FROM_0_0_x_TO_0_4_x.md: -------------------------------------------------------------------------------- 1 | ##Overview 2 | 3 | `achambers/ember-cli-deploy`, `LevelBossMike/ember-deploy` and `tedconf/ember-cli-front-end-builds` are coming together to create the offical Ember CLI deployment tool, `ember-cli/ember-cli-deploy`. 4 | 5 | Users upgrading from the `ember-cli-deploy` npm package `<= v0.0.6` to `>= v0.4.0` will need to follow the migration steps below as the core codebase of the package will be changing completely. 6 | 7 | While we are trying our best to maintain backwards compatability for users of `<= v0.0.6`, this will only be temporary and users are strongly urged to migrate ASAP. 8 | 9 | ## Migrate config 10 | 11 | Migrate your `<= v0.0.6` config from this: 12 | 13 | ```javascript 14 | // config/deploy/staging.js 15 | 16 | module.exports = { 17 | assets: { 18 | accessKeyId: process.env.AWS_ACCESS_KEY, 19 | secretAccessKey: process.env.AWS_SECRET, 20 | bucket: 'staging-bucket', 21 | region: 'eu-west-1' 22 | }, 23 | 24 | index: { 25 | host: 'staging-redis.example.com', 26 | port: '1234' 27 | } 28 | }; 29 | ``` 30 | 31 | ```javascript 32 | // config/deploy/production.js 33 | 34 | module.exports = { 35 | assets: { 36 | accessKeyId: process.env.AWS_ACCESS_KEY, 37 | secretAccessKey: process.env.AWS_SECRET, 38 | bucket: 'prod-bucket', 39 | region: 'eu-west-1' 40 | }, 41 | 42 | index: { 43 | host: 'production-redis.example.com', 44 | port: '9876', 45 | password: process.env.REDIS_PASSWORD 46 | } 47 | }; 48 | ``` 49 | 50 | to this: 51 | 52 | ```javascript 53 | // config/deploy.js 54 | 55 | module.exports = { 56 | staging: { 57 | buildEnv: 'staging', 58 | store: { 59 | host: 'staging-redis.example.com', 60 | port: 1234 61 | }, 62 | assets: { 63 | accessKeyId: process.env.AWS_ACCESS_KEY, 64 | secretAccessKey: process.env.AWS_SECRET, 65 | bucket: 'staging-bucket' 66 | region: 'eu-west-1' 67 | } 68 | }, 69 | 70 | production: { 71 | store: { 72 | host: 'production-redis.example.com', 73 | port: 9876, 74 | password: process.env.REDIS_PASSWORD 75 | }, 76 | assets: { 77 | accessKeyId: process.env.AWS_ACCESS_KEY, 78 | secretAccessKey: process.env.AWS_SECRET, 79 | bucket: 'prod-bucket' 80 | region: 'eu-west-1' 81 | } 82 | } 83 | }; 84 | ``` 85 | 86 | ## Migrate adapters 87 | 88 | Uninstall the now unsupported adapters: 89 | 90 | ```shell 91 | $ npm uninstall ember-cli-deploy-redis-index-adapter --save-dev 92 | ``` 93 | 94 | And install the corresponding supported plugins: 95 | 96 | ```shell 97 | $ npm install ember-deploy-redis --save-dev 98 | 99 | $ npm install ember-deploy-s3 --save-dev 100 | ``` 101 | 102 | ## Serving of index.html 103 | 104 | Due to the way `v0.4.0` now stores the list of previous revisions, [achambers/fuzzy-wookie](https://github.com/achambers/fuzzy-wookie) is no longer compatible with ember-cli-deploy. 105 | 106 | If you were using [achambers/fuzzy-wookie](https://github.com/achambers/fuzzy-wookie), please migrate to use [philipheinser/ember-lightning](https://github.com/philipheinser/ember-lightning) instead. 107 | 108 | If you wrote your own server to serve the index.html, you will need to modify it in order for it to work with `v0.4.0`. The breaking change is that instead of storing just `sha` in the list of previous revisions,ember-cli-deploy now stores `app-name:sha`. Please make any changes necessary to your server to support this change. 109 | 110 | ## Unsupported commands 111 | 112 | A number of commands became deprecated in `v0.4.0` and will become unsupported in future versions very soon. 113 | 114 | - instead of `ember deploy:index` and `ember deploy:assets`, please use `ember deploy` 115 | - instead of `ember activate`, please use `ember deploy:activate` 116 | - instead of `ember deploy:versions`, please use `ember deploy:list` 117 | -------------------------------------------------------------------------------- /lib/tasks/pipeline.js: -------------------------------------------------------------------------------- 1 | var Task = require('ember-cli/lib/models/task'); 2 | var SilentError = require('silent-error'); 3 | var Pipeline = require('../models/pipeline'); 4 | 5 | module.exports = Task.extend({ 6 | init: function() { 7 | if (!this.project) { 8 | throw new SilentError('No project passed to pipeline task'); 9 | } 10 | 11 | if (!this.ui) { 12 | throw new SilentError('No ui passed to pipeline task'); 13 | } 14 | 15 | if(!this.deployTarget) { 16 | throw new SilentError('You need to provide a deployTarget: `ember deploy production`'); 17 | } 18 | 19 | if(!this.config) { 20 | throw new SilentError('No config passed to pipeline task'); 21 | } 22 | 23 | this._pipeline = this.pipeline || new Pipeline(this.hooks, { 24 | ui: this.ui 25 | }); 26 | 27 | this.commandOptions = this.commandOptions || {}; 28 | }, 29 | 30 | setup: function(){ 31 | var self = this; 32 | self._installedPlugins = self._discoverInstalledPlugins(); 33 | 34 | var plugins; 35 | var configuredPluginList = this.config.plugins; 36 | if (configuredPluginList) { 37 | plugins = self._configuredPlugins(configuredPluginList, self._installedPlugins); 38 | } else { 39 | // no whitelist available, use all autodiscovered plugins 40 | plugins = self._installedPlugins; 41 | } 42 | 43 | var pluginNames = Object.keys(plugins); 44 | 45 | if (pluginNames.length === 0) { 46 | self.ui.writeError("\nWARNING: No plugins installed.\n"); 47 | self.ui.writeError("ember-cli-deploy works by registering plugins in its pipeline."); 48 | self.ui.writeError("In order to execute a deployment you must install at least one ember-cli-deploy compatible plugin.\n"); 49 | self.ui.writeError("Visit http://ember-cli.github.io/ember-cli-deploy/docs/v0.5.x/plugins/ for a list of supported plugins.\n"); 50 | } 51 | 52 | pluginNames.forEach(function(pluginName) { 53 | self._registerPipelineHooks(plugins[pluginName], pluginName); 54 | }); 55 | }, 56 | 57 | _discoverInstalledPlugins: function(){ 58 | var self = this; 59 | var installedPlugins = Object.create(null); 60 | (this.project.addons || []).forEach(function(addon){ 61 | self._registerInstalledPlugin(addon, installedPlugins); 62 | }); 63 | return installedPlugins; 64 | }, 65 | 66 | _registerInstalledPlugin: function(addon, registry) { 67 | var self = this; 68 | 69 | if (this._isDeployPluginPack(addon)) { 70 | addon.addons.forEach(function(nestedAddon){ 71 | self._registerInstalledPlugin(nestedAddon, registry); 72 | }); 73 | return; 74 | } 75 | 76 | if (!this._isValidDeployPlugin(addon)) { return; } 77 | 78 | registry[this._pluginNameFromAddonName(addon)] = addon; 79 | }, 80 | 81 | run: function() { 82 | var pipeline = this._pipeline; 83 | var ui = this.ui; 84 | var project = this.project; 85 | var commandOptions = this.commandOptions; 86 | 87 | this.setup(); 88 | var context = { 89 | commandOptions: commandOptions, 90 | config: this.config, 91 | deployTarget: this.deployTarget, 92 | project: project, 93 | ui: ui 94 | }; 95 | return pipeline.execute(context); 96 | }, 97 | 98 | _configuredPlugins: function(configuredPluginNames, availablePlugins) { 99 | var unavailablePlugins = Object.create(null); 100 | var configuredPlugins = Object.create(null); 101 | 102 | configuredPluginNames.forEach(function(configuredPluginKey) { 103 | var parts = configuredPluginKey.split(':', 2); 104 | var pluginName = parts[0]; 105 | var configuredName = parts[1] ? parts[1] : parts[0]; 106 | 107 | if (availablePlugins[pluginName]) { 108 | configuredPlugins[configuredName] = availablePlugins[pluginName]; 109 | } else { 110 | unavailablePlugins[configuredName] = pluginName; 111 | } 112 | }); 113 | 114 | if (Object.keys(unavailablePlugins).length !== 0) { 115 | this.ui.writeError(unavailablePlugins); 116 | var error = new SilentError('plugins configuration references plugins which are not available.'); 117 | error.unavailablePlugins = unavailablePlugins; 118 | throw error; 119 | } 120 | 121 | return configuredPlugins; 122 | }, 123 | 124 | _pluginNameFromAddonName: function(addon) { 125 | var pluginNameRegex = /^(ember\-cli\-deploy\-)(.*)$/; 126 | return addon.name.match(pluginNameRegex)[2]; 127 | }, 128 | 129 | _registerPipelineHooks: function(addon, alias) { 130 | var deployPlugin = addon.createDeployPlugin({name: alias}); 131 | 132 | this._pipeline.hookNames().forEach(function(hookName) { 133 | var fn = deployPlugin[hookName]; 134 | if (typeof fn !== 'function') { 135 | return; 136 | } 137 | 138 | this._pipeline.register(hookName, { 139 | name: deployPlugin.name, 140 | fn: function(context){ 141 | if (deployPlugin.beforeHook && typeof deployPlugin.beforeHook === 'function') { 142 | deployPlugin.beforeHook(context); 143 | } 144 | return fn.call(deployPlugin, context); 145 | } 146 | }); 147 | }.bind(this)); 148 | }, 149 | 150 | _isDeployPluginPack: function(addon) { 151 | return this._addonHasKeyword(addon, 'ember-cli-deploy-plugin-pack'); 152 | }, 153 | 154 | _isValidDeployPlugin: function(addon) { 155 | return this._addonHasKeyword(addon, 'ember-cli-deploy-plugin') && this._addonImplementsDeploymentHooks(addon); 156 | }, 157 | 158 | _addonHasKeyword: function(addon, keyword) { 159 | var keywords = addon.pkg.keywords; 160 | return keywords.indexOf(keyword) > -1; 161 | }, 162 | 163 | _addonImplementsDeploymentHooks: function(addon) { 164 | return addon.createDeployPlugin && typeof addon.createDeployPlugin === 'function'; 165 | } 166 | }); 167 | -------------------------------------------------------------------------------- /lib/models/pipeline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('ember-cli/lib/ext/promise'); 4 | var _ = require('lodash-node'); 5 | 6 | var chalk = require('chalk'); 7 | var blue = chalk.blue; 8 | var red = chalk.red; 9 | 10 | /* This is a generic implementation of a pipeline with ordered, promise-aware hooks, 11 | * pleasant logging, and failure handling. It should not have any "deployment" domain 12 | * logic or semantics, and is a candidate for extraction to its own npm module. 13 | */ 14 | function Pipeline(hookNames, options) { 15 | hookNames = hookNames || []; 16 | options = options || {}; 17 | 18 | this._ui = options.ui; 19 | 20 | this._pipelineHooks = hookNames.reduce(function(pipelineHooks, hookName) { 21 | pipelineHooks[hookName] = []; 22 | 23 | return pipelineHooks; 24 | }, { didFail: [] }); 25 | 26 | this._progressBarLib = options.progressBarLib || require('ember-cli-deploy-progress'); 27 | } 28 | 29 | Pipeline.prototype.register = function(hookName, fn) { 30 | var ui = this._ui; 31 | var pipelineHooks = this._pipelineHooks; 32 | 33 | if (typeof fn === 'function') { 34 | fn = { 35 | name: 'anonymous function', 36 | fn: fn 37 | }; 38 | } 39 | 40 | if (pipelineHooks[hookName]) { 41 | if (ui.verbose) { 42 | ui.write(blue('Registering hook -> ' + hookName + '[' + fn.name + ']\n')); 43 | } 44 | 45 | pipelineHooks[hookName].push(fn); 46 | } 47 | }; 48 | 49 | Pipeline.prototype.execute = function(context) { 50 | context = context || { }; 51 | 52 | var ui = this._ui; 53 | var hooks = this._hooksWithoutDidFail(this.hookNames()); 54 | var ProgressBar = this._progressBarLib; 55 | if (ui.verbose) { 56 | ui.write(blue('Executing pipeline\n')); 57 | } else if (ui.showProgress) { 58 | ui.progressBar = new ProgressBar('Deploying [:bar] :percent [plugin: :plugin -> :hook]', { 59 | total: this._hooksCount(this._hooksWithoutConfigure(hooks)), 60 | cursor: process.platform === 'darwin' ? '🚀 ' : '>' 61 | }); 62 | } 63 | 64 | return hooks.reduce(this._addHookExecutionPromiseToPipelinePromiseChain.bind(this, ui), Promise.resolve(context)) 65 | .then(this._notifyPipelineCompletion.bind(this, ui)) 66 | .catch(this._handlePipelineFailure.bind(this, ui, context)) 67 | .catch(this._abortPipelineExecution.bind(this, ui)); 68 | }; 69 | 70 | Pipeline.prototype.hookNames = function() { 71 | return Object.keys(this._pipelineHooks); 72 | }; 73 | 74 | Pipeline.prototype._addHookExecutionPromiseToPipelinePromiseChain = function(ui, promise, hookName) { 75 | var self = this; 76 | return promise 77 | .then(this._notifyPipelineHookExecution.bind(this, ui, hookName)) 78 | .then(function(context){ 79 | try { 80 | return self._executeHook(hookName, context); 81 | } catch(error) { 82 | return Promise.reject(error); 83 | } 84 | }); 85 | }; 86 | 87 | Pipeline.prototype._hooksWithoutDidFail = function(hooks) { 88 | return hooks.filter(function(hook) { 89 | return hook !== 'didFail'; 90 | }); 91 | }; 92 | 93 | Pipeline.prototype._hooksWithoutConfigure = function(hooks) { 94 | return hooks.filter(function(hook) { 95 | return hook !== 'configure'; 96 | }); 97 | }; 98 | 99 | Pipeline.prototype._hooksCount = function(hooks) { 100 | return hooks.reduce(function(sum, hookName) { 101 | var hookFunctions = this._pipelineHooks[hookName]; 102 | return sum + hookFunctions.length; 103 | }.bind(this), 0); 104 | }; 105 | 106 | Pipeline.prototype._handlePipelineFailure = function(ui, context, error) { 107 | if (ui.verbose) { 108 | ui.write(red('|\n')); 109 | ui.write(red('+- didFail\n')); 110 | } 111 | ui.write(red(error + '\n' + (error ? error.stack : null))); 112 | return this._executeHook('didFail', context) 113 | .then(Promise.reject.bind(this, error)); 114 | }; 115 | 116 | Pipeline.prototype._abortPipelineExecution = function(ui/*, error */) { 117 | if (ui.verbose) { 118 | ui.write(blue('|\n')); 119 | } 120 | ui.write(red('Pipeline aborted\n')); 121 | return Promise.reject(); 122 | }; 123 | 124 | Pipeline.prototype._notifyPipelineCompletion = function(ui) { 125 | if (ui.verbose) { 126 | ui.write(blue('|\n')); 127 | ui.write(blue('Pipeline complete\n')); 128 | } 129 | }; 130 | 131 | Pipeline.prototype._notifyPipelineHookExecution = function(ui, hookName, context) { 132 | if (ui.verbose) { 133 | ui.write(blue('|\n')); 134 | ui.write(blue('+- ' + hookName + '\n')); 135 | } 136 | return context; 137 | }; 138 | 139 | Pipeline.prototype._executeHook = function(hookName, context) { 140 | var ui = this._ui; 141 | var hookFunctions = this._pipelineHooks[hookName]; 142 | 143 | return hookFunctions.reduce(this._addPluginHookExecutionPromiseToHookPromiseChain.bind(this, ui, context, hookName), Promise.resolve(context)); 144 | }; 145 | 146 | Pipeline.prototype._addPluginHookExecutionPromiseToHookPromiseChain = function(ui, context, hookName, promise, fnObject) { 147 | return promise 148 | .then(this._notifyPipelinePluginHookExecution.bind(this, ui, fnObject, hookName)) 149 | .then(this._mergePluginHookResultIntoContext.bind(this, context)); 150 | }; 151 | 152 | Pipeline.prototype._notifyPipelinePluginHookExecution = function(ui, fnObject, hookName, context) { 153 | if (ui.verbose) { 154 | ui.write(blue('| |\n')); 155 | ui.write(blue('| +- ' + fnObject.name + '\n')); 156 | } else if (ui.showProgress) { 157 | if (hookName !== 'configure') { 158 | ui.progressBar.tick({ 159 | hook: hookName, 160 | plugin: fnObject.name 161 | }); 162 | } 163 | } 164 | 165 | return fnObject.fn(context); 166 | }; 167 | 168 | Pipeline.prototype._mergePluginHookResultIntoContext = function(context,result) { 169 | return _.merge(context, result, function(a, b) { 170 | if (_.isArray(a)) { 171 | return a.concat(b); 172 | } 173 | }); 174 | }; 175 | 176 | module.exports = Pipeline; 177 | -------------------------------------------------------------------------------- /node-tests/unit/models/pipeline-test.js: -------------------------------------------------------------------------------- 1 | var Promise = require('ember-cli/lib/ext/promise'); 2 | var Pipeline = require('../../../lib/models/pipeline'); 3 | 4 | var expect = require('../../helpers/expect'); 5 | var FakeProgressBar = require('../../helpers/fake-progress-bar'); 6 | 7 | describe ('Pipeline', function() { 8 | describe ('initialization', function() { 9 | it ('initializes the given list of hooks plus the `didFail`-hook', function() { 10 | var subject = new Pipeline(['willDeploy', 'didDeploy']); 11 | 12 | expect(Object.keys(subject._pipelineHooks).length).to.eq(3); 13 | expect(subject._pipelineHooks.willDeploy).to.eql([]); 14 | expect(subject._pipelineHooks.didDeploy).to.eql([]); 15 | expect(subject._pipelineHooks.didFail).to.eql([]); 16 | }); 17 | }); 18 | 19 | describe ('#register', function() { 20 | it ('registers functions for defined hooks', function() { 21 | var subject = new Pipeline(['willDeploy'], { 22 | ui: {write: function() {}} 23 | }); 24 | var fn = function() {}; 25 | 26 | subject.register('willDeploy', fn); 27 | 28 | expect(subject._pipelineHooks.willDeploy.length).to.eq(1); 29 | expect(subject._pipelineHooks.willDeploy[0].name).to.eq('anonymous function'); 30 | expect(subject._pipelineHooks.willDeploy[0].fn).to.eql(fn); 31 | }); 32 | 33 | it ('doesn\'t register functions for hooks not defined', function() { 34 | var subject = new Pipeline(['willDeploy'], { 35 | ui: {write: function() {}} 36 | }); 37 | var fn = function() {}; 38 | 39 | subject.register('build', fn); 40 | 41 | expect(subject._pipelineHooks.willDeploy.length).to.eq(0); 42 | expect(subject._pipelineHooks.build).to.eq(undefined); 43 | }); 44 | }); 45 | 46 | describe ('#execute', function() { 47 | it('runs the registered functions', function() { 48 | var subject = new Pipeline(['hook1', 'hook2'], {ui: {write: function() {}}}); 49 | var hooksRun = []; 50 | 51 | subject.register('hook1', function() { 52 | hooksRun.push('1'); 53 | }); 54 | 55 | subject.register('hook2', function() { 56 | hooksRun.push('2'); 57 | }); 58 | return expect(subject.execute()).to.be.fulfilled 59 | .then(function() { 60 | expect(hooksRun.length).to.eq(2); 61 | expect(hooksRun[0]).to.eq('1'); 62 | expect(hooksRun[1]).to.eq('2'); 63 | }); 64 | }); 65 | 66 | it('executes the `didFail`-hook as soon as one of the pipeline hooks rejects', function() { 67 | var subject = new Pipeline(['hook1', 'hook2'], {ui: {write: function() {}}}); 68 | var hooksRun = []; 69 | 70 | subject.register('hook1', function() { 71 | hooksRun.push('hook1'); 72 | }); 73 | 74 | subject.register('hook2', function() { 75 | return Promise.reject(); 76 | }); 77 | 78 | subject.register('hook3', function() { 79 | hooksRun.push('3'); 80 | }); 81 | 82 | subject.register('didFail', function() { 83 | hooksRun.push('didFail'); 84 | }); 85 | 86 | return expect(subject.execute()).to.be.rejected 87 | .then(function() { 88 | expect(hooksRun.length).to.eq(2); 89 | expect(hooksRun[0]).to.eq('hook1'); 90 | expect(hooksRun[1]).to.eq('didFail'); 91 | }); 92 | }); 93 | 94 | it('passes the default context object when one isn\'t provided', function() { 95 | var subject = new Pipeline(['hook1'], {ui: {write: function() {}}}); 96 | var data = null; 97 | 98 | subject.register('hook1', function(context) { 99 | data = context; 100 | }); 101 | 102 | return expect(subject.execute()).to.be.fulfilled 103 | .then(function() { 104 | expect(data).to.deep.equal({}); 105 | }); 106 | }); 107 | 108 | it('passes the provided context object to hooks when provided', function() { 109 | var subject = new Pipeline(['hook1'], {ui: {write: function() {}}}); 110 | var data = null; 111 | 112 | subject.register('hook1', function(context) { 113 | data = context; 114 | }); 115 | 116 | return expect(subject.execute({deploy: {}})).to.be.fulfilled 117 | .then(function() { 118 | expect(data).to.deep.equal({deploy: {}}); 119 | }); 120 | }); 121 | 122 | it('merges the return value (object) of each hook into the context', function() { 123 | var subject = new Pipeline(['hook1'], {ui: {write: function() {}}}); 124 | var finalContext = null; 125 | 126 | subject.register('hook1', function() { 127 | return {age: 47}; 128 | }); 129 | 130 | subject.register('hook1', function(context) { 131 | finalContext = context; 132 | }); 133 | 134 | return expect(subject.execute({name: 'test-context'})).to.be.fulfilled 135 | .then(function() { 136 | expect(finalContext.name).to.equal('test-context'); 137 | expect(finalContext.age).to.equal(47); 138 | }); 139 | }); 140 | 141 | it('merges the return value (promise) of each hook into the context', function() { 142 | var subject = new Pipeline(['hook1'], {ui: {write: function() {}}}); 143 | var finalContext = null; 144 | 145 | subject.register('hook1', function() { 146 | return Promise.resolve({age: 47}); 147 | }); 148 | 149 | subject.register('hook1', function(context) { 150 | finalContext = context; 151 | }); 152 | 153 | return expect(subject.execute({name: 'test-context'})).to.be.fulfilled 154 | .then(function() { 155 | expect(finalContext.name).to.equal('test-context'); 156 | expect(finalContext.age).to.equal(47); 157 | }); 158 | }); 159 | }); 160 | 161 | describe('#hookNames', function() { 162 | it('returns the names of the registered hooks', function() { 163 | var subject = new Pipeline(['hook1', 'hook2']); 164 | 165 | var result = subject.hookNames(); 166 | 167 | expect(result).to.have.members(['hook1', 'hook2', 'didFail']); 168 | }); 169 | }); 170 | 171 | describe('progressBar', function() { 172 | it('is initalized if showProgress is true', function() { 173 | var ui = { 174 | showProgress: true, 175 | }; 176 | var subject = new Pipeline(['hook1'], { 177 | ui: ui, 178 | progressBarLib: FakeProgressBar 179 | }); 180 | 181 | subject.execute(); 182 | 183 | expect(ui.progressBar).to.be.ok; 184 | }); 185 | 186 | it('is not used in --verbose mode', function() { 187 | var ui = { 188 | verbose: true, 189 | showProgress: true, 190 | write: function() {} 191 | }; 192 | var subject = new Pipeline(['hook1'], { 193 | ui: ui, 194 | progressBarLib: FakeProgressBar 195 | }); 196 | 197 | subject.execute(); 198 | 199 | expect(ui.progressBar).to.be.falsy; 200 | }); 201 | 202 | it('calculates the total number of registered hooks functions', function() { 203 | var ui = { 204 | showProgress: true, 205 | }; 206 | var subject = new Pipeline(['hook1', 'hook2'], { 207 | ui: ui, 208 | showProgress: true, 209 | progressBarLib: FakeProgressBar 210 | }); 211 | 212 | subject.register('hook1', function() {}); 213 | subject.register('hook1', function() {}); 214 | subject.register('hook2', function() {}); 215 | subject.execute(); 216 | 217 | expect(ui.progressBar.total).to.equal(3); 218 | }); 219 | 220 | it('excludes the configure hooks from the total', function() { 221 | var ui = { 222 | showProgress: true, 223 | }; 224 | var subject = new Pipeline(['configure', 'hook1'], { 225 | ui: ui, 226 | showProgress: true, 227 | progressBarLib: FakeProgressBar 228 | }); 229 | 230 | subject.register('hook1', function() {}); 231 | subject.register('hook1', function() {}); 232 | subject.register('configure', function() {}); 233 | subject.execute(); 234 | 235 | expect(ui.progressBar.total).to.equal(2); 236 | }); 237 | 238 | it('ticks during execution', function() { 239 | var ui = { 240 | showProgress: true, 241 | }; 242 | var subject = new Pipeline(['hook1', 'hook2'], { 243 | ui: ui, 244 | showProgress: true, 245 | progressBarLib: FakeProgressBar 246 | }); 247 | 248 | var fn1 = { 249 | fn: function() {}, 250 | name: 'fn1' 251 | }; 252 | var fn2 = { 253 | fn: function() {}, 254 | name: 'fn2' 255 | }; 256 | 257 | subject.register('hook1', fn1); 258 | subject.register('hook2', fn2); 259 | 260 | return expect(subject.execute()).to.be.fulfilled 261 | .then(function() { 262 | expect(ui.progressBar.ticks).to.eql( 263 | [{hook: 'hook1', plugin: 'fn1'}, 264 | {hook: 'hook2', plugin: 'fn2'} 265 | ] 266 | ); 267 | }); 268 | }); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /rfc/plugins.md: -------------------------------------------------------------------------------- 1 | # Plugins for ember-cli-deploy 2 | 3 | This document describes the API related to plugins in the next 4 | version of ember-cli-deploy. 5 | 6 | ### Overview of plugins 7 | 8 | A plugin is an ember-cli addon that hooks into the ember-cli-deploy 9 | pipeline in order to add functionality to a deploy. Example addon 10 | functionality would be uploading assets to S3, writing the index.html to 11 | Redis, or notifying a Slack channel a deploy has completed. 12 | 13 | In general, OSS plugins should focus on doing a specific task. Most 14 | Ember developers with common deployment targets will compose multiple 15 | plugins to fine tune their deployment process. Developers with very 16 | custom needs might create a single private plugin that implements all 17 | aspects of their deployment process within the structure provided by 18 | ember-cli-deploy. 19 | 20 | Because plugins are implemented via addons, they may be included via 21 | node_module dependencies or via in-repo-addons. 22 | 23 | ### Identifying plugins 24 | 25 | Plugins are ember-cli addons. They will also have the keyword 26 | `ember-cli-deploy-plugin`. An example `package.json` for a plugin: 27 | 28 | ``` 29 | { 30 | "name": "ember-cli-deploy-example-plugin", 31 | "version": "0.4.0", 32 | // ... 33 | "devDependencies": { 34 | "ember-cli": "0.2.0", 35 | "ember-cli-deploy": "0.4.1" 36 | // ... 37 | }, 38 | "keywords": [ 39 | "ember-addon", 40 | "ember-cli-deploy-plugin" 41 | ] 42 | } 43 | ``` 44 | 45 | By default, any plugin that is installed will be loaded, similar to 46 | ember-cli addons. The plugin order is determined by how ember-cli orders 47 | addons (based on `before:`/`after:` properties). To override this, see 48 | Advanced Plugin Configuration and Ordering below. 49 | 50 | ### Plugins are provided by ember-cli addons 51 | 52 | Plugins are ember-cli addons which implement an `createDeployPlugin()` method 53 | to return an object which implements one or more methods that are called by 54 | ember-cli-deploy. For example: 55 | 56 | ```javascript 57 | module.exports = { 58 | name: 'ember-cli-deploy-example-plugin', 59 | createDeployPlugin: function(options){ 60 | return { 61 | name: options.name, 62 | willDeploy: function:(deployment){ 63 | // do something during the willDeploy phase of the pipeline 64 | }, 65 | didDeploy: function:(deployment){ 66 | // do something during the didDeploy phase of the pipeline 67 | }, 68 | // etc, see hooks section for a complete list of methods that 69 | // may be implemented by a plugin 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | This approach limits the risk of name conflicts at the top-level of the addon. 76 | It also allows for the plugin author to consult the addon instance during 77 | creation of the plugin object to make any contextual decisions necessary. 78 | Finally, it is agnostic with respect to the type of object the plugin is. 79 | It may be a POJO, a subclass of CoreObject, or maybe an ES6 class. 80 | 81 | The `options` argument passed to `createDeployPlugin` will have a `name` 82 | property. Usually, the `name` will be the plugin name sans the `ember-cli-deploy-` 83 | prefix, unless a name has been specified as described in Advanced Plugin 84 | Configuration below. 85 | 86 | ### The `context` object 87 | 88 | For each high-level ember-cli-deploy operation, a `context` object is created. 89 | This object is passed to each hook that is invoked on the plugins. It has a number 90 | of properties that may be of use to a plugin: 91 | 92 | Property | file | info 93 | --- | --- | --- 94 | `ui` | - | The ember-cli UI object that can be used to write to stdout. 95 | `config` | stored in `config/deploy.js` | The configuration portion of `config/deploy.js` for the active environment 96 | `data` | - | Runtime information about the current operation. Plugins can set properties on this object for later use by themselves or another plugin. 97 | 98 | ### Async operations in hooks 99 | 100 | Hook functions can return a promise to block the deployment pipeline. 101 | Since most deployment involves some sort of IO it makes senses that most 102 | plugins will want an async function to complete before continuing to the 103 | next step. If a plugin does not return a promise, then ember-cli-deploy 104 | proceeds immediately. 105 | 106 | If a promise from any of the plugins is rejected then the deployment 107 | pipeline will stop and ember-cli-deploy will exit. Returned promises that are 108 | rejected are treated as unrecoverable errors. 109 | 110 | ### Configuration 111 | 112 | By convention, plugin configuration should be kept in `config/deploy.js` and scoped by 113 | the plugin's name. e.g. for an `ember-cli-deploy-example` plugin, the configuration might look like: 114 | 115 | ```javascript 116 | // config/deploy.js 117 | module.exports = return function(environment) { 118 | return { 119 | "example": { 120 | bucket: "my-app-" + environment, 121 | awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID, 122 | awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY 123 | } 124 | }; 125 | }; 126 | ``` 127 | 128 | ### Hooks 129 | 130 | These hooks (part of a typical deployment process) are available for plugins to implement: 131 | 132 | ``` 133 | configure: ---> runs before anything happens 134 | 135 | setup: -------> the first hook for every command 136 | 137 | willDeploy: --> runs before anything happens. good opportunity for plugins to validate 138 | configuration or other preconditions 139 | 140 | /-- willBuild confirm environment 141 | / 142 | build --------> builds app assets, documentation, etc. 143 | \ 144 | \-- didBuild manipulate index.html, validate assets 145 | 146 | /-- willPrepare confirm deployment info 147 | / 148 | prepare --------> prepare information about the deploy, eg revisonKey, timestamp, commit message 149 | \ 150 | \-- didPrepare notify APIS (slack etc) 151 | 152 | /-- willUpload confirm remote servers(S3, Redis, Azure, etc.) 153 | / 154 | upload -------> puts the assets somewhere(S3, Redis, Azure, Rackspace, etc.) 155 | | Note: a plugin that implements upload of the HTML file and 156 | | wants to support version activation should set 157 | | `currentVersion` on the `deployment` object to the ID 158 | | of the newly deployed version. 159 | \ 160 | \-- didUpload notify APIs (slack, pusher, etc.), warm cache 161 | 162 | /-- willActivate create backup of assets, notify APIs, uninstall earlier versions 163 | / 164 | activate -------> make a new version live (clear cache, swap Redis values, etc.) 165 | \ 166 | \-- didActivate notify APIs, warm cache 167 | 168 | Note: when hooks in the activate series of hooks are called, the plugin can assume the 169 | presence of a `currentVersion` property on the deployment object, that is set to 170 | the ID of the version to be activated. 171 | 172 | didDeploy: --> runs at the end of a full deployment operation. 173 | 174 | teardown: ---> always the last hook being run 175 | ``` 176 | 177 | In addition, there are a few more specialized hooks that plugins may implement: 178 | 179 | ``` 180 | discoverVersions: --> should return a promise resolving to an array of version objects. Each 181 | version object _must_ have an `id` property. Each version _may_ have one 182 | or more of the following properties: 183 | 184 | `timestamp`: (Date) when the version was created 185 | `revision`: (String) reference of version in SCM 186 | `creator`: (String) email address of developer who deployed the version 187 | `description`: (String) summary of the version 188 | 189 | ``` 190 | 191 | ### Hooks by command 192 | 193 | Depending on the command you're running the hooks that are called vary 194 | 195 | #### `ember deploy` 196 | ``` 197 | - configure 198 | - setup 199 | - willDeploy 200 | - willBuild, build, didBuild, 201 | - willPrepare, prepare, didPrepare, 202 | - willUpload, upload, didUpload, 203 | - willActivate, activate, didActivate, (only if --activate flag is passed) 204 | - didDeploy, 205 | - teardown 206 | ``` 207 | 208 | #### `ember deploy:activate` 209 | ``` 210 | - configure 211 | - setup 212 | - willActivate, activate, didActivate 213 | - teardown 214 | ``` 215 | 216 | #### `ember deploy:list` 217 | ``` 218 | - configure 219 | - setup 220 | - fetchRevisions 221 | - displayRevisions 222 | - teardown 223 | ``` 224 | 225 | ### Advanced Plugin Configuration 226 | 227 | As mentioned above, by default, all plugins from installed addons will be loaded, and 228 | ordered based on ember-cli's order of the addons. Developers may have advanced use cases 229 | for specifying the order of plugins, disabling plugins, or configuring a single plugin to 230 | be configured and used twice. 231 | 232 | If you want to opt-into this configuration, you can set the `plugins` property in your `config/deploy.js` file at either the top-level (for global configuration), or under an environment (for per-environment configuration). 233 | 234 | ``` 235 | plugins: ["s3-assets", "s3-index", "notify-slack"] 236 | ``` 237 | 238 | Any plugins not included in the list will not have their hooks executed. 239 | 240 | #### Aliasing plugins 241 | 242 | To include a plugin twice, alias it using a colon. 243 | 244 | ``` 245 | plugins: ["s3-assets:foo-assets", "s3-assets:bar-assets", "s3-index", "notify-slack"] 246 | ``` 247 | 248 | The name specified after the colon will be passed as the `name` property 249 | of the `options` argument to the addon's `createDeployPlugin` method. Plugins 250 | should use their name to retrieve configuration values. In this example, 251 | the foo-assets instance of the s3-assets plugin could have different configuration 252 | than the bar-assets instance does. 253 | 254 | ### Another take on configuration 255 | 256 | Convention-over-configuration is of utmost importance. For golden path use cases, including the packages and doing unavoidable configuration (e.g. credentials and connection info) should be all it takes. Of course, it should be possible to wire together plugins in ways beside the golden path, too. 257 | 258 | Here's how we should accomplish this. 259 | 260 | Goals: 261 | 262 | 1) A great out-of-the-box app developer experience for common deployment scenarios. 263 | 2) An API for app developers to define deployment pipeline configuration synchronously or asynchronously. 264 | 3) An API for plugin developers to provide static default configuration values. 265 | 4) An API for plugin developers to provide default configuration values that are derived at run-time from the data produced by plugin running prior to it in the pipeline. 266 | 5) An API for plugin developers to allow ap developers to interact with plugin settings via command line flags. 267 | 6) An API for app developers to specify configuration of a plugin to use data produced by a plugin running prior to it in the pipeline. 268 | 269 | Approach: 270 | 271 | * App developers use `config/deploy.js` to return a function that receives the build environment as a string and returns either a config object or a Promise that fulfills with a config object. The config object has properties corresponding to the name of the plugin (e.g. for ember-cli-deploy-redis, the property is “redis”). Supports goal No. 2 above. (This is implemented in the 0.5.0 WIP already.) 272 | 273 | Examples: 274 | 275 | ```js 276 | // deploy.js (sync) 277 | module.export function(environment){ 278 | var ENV = { 279 | redis: { 280 | url: process.env.REDIS_URL 281 | } 282 | } 283 | return ENV; 284 | }; 285 | 286 | // deploy.js (async) 287 | module.export function(environment){ 288 | var ENV = { 289 | redis: { 290 | } 291 | } 292 | return someAsyncDataRetrieval(environment).then(function(data){ 293 | ENV.redis = data.redisUrl; 294 | return ENV; 295 | } 296 | }; 297 | ``` 298 | 299 | * Plugin developers can implement a `configure` hook that runs at the beginning of pipeline execution (because “configure” is the first step of the pipeline). This hook has read/write access to the config object. It can specify default configuration values, as well as throw an Error in the case that a required configuration property was not provided. Supports goal No. 3 above. (This is implemented in the 0.5.0 WIP already, although we should provide plugins with helper to define defaults and enforce required properties more expressively and more consistently with other plugins.) 300 | 301 | Example: 302 | 303 | ```js 304 | // some-ember-cli-deploy-plugin/index.js 305 | module.exports = { 306 | name: 'ember-cli-deploy-myplugin', 307 | 308 | createDeployPlugin: function(options) { 309 | return { 310 | name: options.name, 311 | configure: function(context) { 312 | var deployment = context.deployment; 313 | var config = deployment.config[this.name] = deployment.config[this.name] || {}; 314 | config.filePattern = config.filePattern || “**/*.html”; // provide default 315 | 316 | }, 317 | // ... 318 | } 319 | }; 320 | ``` 321 | 322 | * Plugin developers can also use `configure` hook specify a default configuration property as a function, which will be called at run-time, when a plugin wishes to read and use the configuration value. The function will receive the context and must return a value or throw an Error. The context would allow access to data added to pipeline by previous plugins, as well as flags set on command line. Supports goal No. 4 and No. 5 above. (This is not yet implemented.) 323 | 324 | Example: 325 | 326 | ```js 327 | // some-ember-cli-deploy-plugin/index.js 328 | module.exports = { 329 | name: 'ember-cli-deploy-myplugin', 330 | 331 | createDeployPlugin: function(options) { 332 | return { 333 | name: options.name, 334 | configure: function(context) { 335 | var deployment = context.deployment; 336 | var config = deployment.config[this.name] = deployment.config[this.name] || {}; 337 | config.revision = config.revision || function(context){ 338 | return context.deployment.revision; 339 | }; 340 | // we could also provide a helper for this, e.g. 341 | // config.revision = config.revision || fromPipelineData(context, “revision”); 342 | config.shouldActivate = config.shouldActivate || function(context){ 343 | return !!context.flags.activate; // set via `--activate on command line 344 | }; 345 | }, 346 | // ... 347 | } 348 | }; 349 | ``` 350 | 351 | * App developers can also use this `function`-style configuration in `config/deploy.js` in order to wire together plugins. The function will receive the context and must return a value or throw an Error. Supports goal No. 6 above. (No additional implementation would be necessary if the above were implemented.) 352 | 353 | Example: 354 | 355 | ```js 356 | // deploy.js 357 | module.export function(environment){ 358 | var ENV = { 359 | redis: { 360 | revisionKey: function(context) { return context.deployment.tag; }, 361 | forceUpdate: function(context) { return context.flags.force; } 362 | } 363 | } 364 | return ENV; 365 | }; 366 | ``` 367 | 368 | These approaches all combine to achieve goal No. 1 above. 369 | 370 | ### Plugin packs 371 | 372 | A "plugin pack" is an ember-cli addon with the keyword 373 | "ember-cli-deploy-plugin-pack" and one or more dependent 374 | addons that are ember-cli-deploy-plugins. 375 | 376 | Note that the plugin pack’s dependent addons should be listed as 377 | dependencies in the pack’s package.json, not as devDependencies. 378 | 379 | Plugin packs may also implement a config/deploy.js blueprint that 380 | is auto-executed upon `ember install` of the pack to make 381 | configuration easy for end-developers. 382 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ember-cli-deploy Changelog 2 | 3 | ## [0.6.0](https://github.com/ember-cli/ember-cli-deploy/tree/0.6.0) (2016-02-26) 4 | [Full Changelog](https://github.com/ember-cli/ember-cli-deploy/compare/v0.5.1...0.6.0) 5 | 6 | This release has no breaking changes from 0.5.1. It introduces a progress bar for deploys run on interactive terminals, improved ability to configure command defaults, and new fetchInitialRevisions & fetchRevisions hooks to the deploy pipeline, which will better enable plugins that want to do changelog/diff notifications. 7 | 8 | We have also introduced new versioned docs for 0.6.x, available now from the version selector on ember-cli-deploy.com in the Docs section. 9 | 10 | Huge thanks to the fine work of the ember-cli-deploy core team, particularly @ghedamat who has been very active this cycle, and to our growing set of contributors and community. It is amazing to the see the ember-cli-deploy plugin ecosystem blossom. 11 | 12 | ### Plugin Authors 13 | 14 | The `fetchRevisions` hook is now be called during the "deploy" and "activate" pipelines. It was previously only called during the "list" pipeline. In addition, a new `fetchInitialRevisions` hook will be called during the "deploy" and "activate" pipelines. See the [0.6.x Pipeline Hooks docs](http://ember-cli.com/ember-cli-deploy/docs/v0.6.x/pipeline-hooks/) for details. If you maintain a plugin that uploads new revisions, you will want to update your plugin to implement the new hook. Here is an example of [updating ember-cli-deploy-redis](https://github.com/ember-cli-deploy/ember-cli-deploy-redis/pull/50). 15 | 16 | You should also update your ember-cli-deploy-plugin dependency to 0.2.2, to ensure your plugin's logging plays nicely with the nifty new progress bar in this ember-cli-deploy release. 17 | 18 | Pull Requests: 19 | 20 | - Move to non-scoped npm package for progress, and newer version. Fixes an issue causing crashes when running at certain narrow terminal widths. [\#366](https://github.com/ember-cli/ember-cli-deploy/pull/366) and [\#367](https://github.com/ember-cli/ember-cli-deploy/pull/367) ([lukemelia](https://github.com/lukemelia)) 21 | - Use our fork of progress so that we can make the rocket part of the progress bar [\#364](https://github.com/ember-cli/ember-cli-deploy/pull/364) ([lukemelia](https://github.com/lukemelia)) 22 | - Remove unused code [\#359](https://github.com/ember-cli/ember-cli-deploy/pull/359) ([achambers](https://github.com/achambers)) 23 | - display progress notification during deploy [\#280](https://github.com/ember-cli/ember-cli-deploy/pull/280) ([ghedamat](https://github.com/ghedamat)) 24 | - add `fetchRevisions`hook to deploy and activate pipelines [\#323](https://github.com/ember-cli/ember-cli-deploy/pull/323) ([sethpollack](https://github.com/sethpollack)) 25 | - Allow loading .env files + .env.deploy.\ files [\#342](https://github.com/ember-cli/ember-cli-deploy/pull/342) ([blimmer](https://github.com/blimmer)) 26 | - read-config task \(fix issue where dotenv files were not loaded in time for deploy tasks\) [\#319](https://github.com/ember-cli/ember-cli-deploy/pull/319) ([lukemelia](https://github.com/lukemelia)) 27 | - Remove temporary fix for broccoli-asset-rev [\#316](https://github.com/ember-cli/ember-cli-deploy/pull/316) ([backspace](https://github.com/backspace)) 28 | 29 | ## [0.5.1](https://github.com/ember-cli/ember-cli-deploy/tree/0.5.1) (2015-11-09) 30 | [Full Changelog](https://github.com/ember-cli/ember-cli-deploy/compare/v0.5.0...0.5.1) 31 | 32 | - Upgrade ember-cli to 1.13.8. [\#296](https://github.com/ember-cli/ember-cli-deploy/pull/296) ([blimmer](https://github.com/blimmer)) 33 | - Require a deploy target to run ember deploy [\#294](https://github.com/ember-cli/ember-cli-deploy/pull/294) ([kiwiupover](https://github.com/kiwiupover)) 34 | - Fix typo in "no plugins" warning \(it's/its\) [\#284](https://github.com/ember-cli/ember-cli-deploy/pull/284) ([pgengler](https://github.com/pgengler)) 35 | 36 | ## [0.5.0](https://github.com/ember-cli/ember-cli-deploy/tree/v0.5.0) (2015-10-29) 37 | [Full Changelog](https://github.com/ember-cli/ember-cli-deploy/compare/v0.4.3...v0.5.0) 38 | 39 | [BREAKING] This release is the first in the 0.5.0 series and the first using the new deploy pipeline and plugin architecture. 40 | 41 | 0.5.0 harnesses the concept of a pipeline that gives users the ability to flexibly compose plugins to satisfy their 42 | custom deployment needs. 43 | 44 | NOTE: 0.5.0 is a major rewrite and shift from the 0.4.x series and hence introduces a number of breaking changes. 45 | 46 | For more information on the ember-cli-deploy v0.5.0, how to use it and how to write plugins please see the brand new 47 | [documentation site](http://ember-cli-deploy.com). 48 | 49 | ## [0.5.0-beta.4](https://github.com/ember-cli/ember-cli-deploy/tree/0.5.0-beta.4) (2015-10-24) 50 | [Full Changelog](https://github.com/ember-cli/ember-cli-deploy/compare/v0.5.0-beta.3...0.5.0-beta.4) 51 | 52 | **Merged pull requests:** 53 | 54 | - Add a `--verbose` option to commands, make default output quiet, and … [\#266](https://github.com/ember-cli/ember-cli-deploy/pull/266) ([lukemelia](https://github.com/lukemelia)) 55 | 56 | ## [0.5.0-beta.3](https://github.com/ember-cli/ember-cli-deploy/tree/0.5.0-beta.3) (2015-10-22) 57 | [Full Changelog](https://github.com/ember-cli/ember-cli-deploy/compare/v0.5.0-beta.2...0.5.0-beta.3) 58 | 59 | This release is expected to be the last beta before 0.5.0. We're happy to welcome 60 | @ghedamat to the core team and are grateful for his many contributions to 61 | documentation this cycle. 62 | 63 | Thanks to everyone who took time to contribute to this release! 64 | 65 | #### Community Contributions 66 | 67 | - add postInstall message [\#256](https://github.com/ember-cli/ember-cli-deploy/pull/256) ([ghedamat](https://github.com/ghedamat)) 68 | - Correct typo; commandOptions are a local var [\#255](https://github.com/ember-cli/ember-cli-deploy/pull/255) ([kategengler](https://github.com/kategengler)) 69 | - Make --deploy-config-path accept absolute paths [\#253](https://github.com/ember-cli/ember-cli-deploy/pull/253) ([dschmidt](https://github.com/dschmidt)) 70 | - Update warning message url when no plugins installed [\#234](https://github.com/ember-cli/ember-cli-deploy/pull/234) ([achambers](https://github.com/achambers)) 71 | - Guard postBuild hook [\#232](https://github.com/ember-cli/ember-cli-deploy/pull/232) ([lukemelia](https://github.com/lukemelia)) 72 | - Merge 0.4.x [\#228](https://github.com/ember-cli/ember-cli-deploy/pull/228) ([lukemelia](https://github.com/lukemelia)) 73 | - Give warning when no plugins are installed [\#225](https://github.com/ember-cli/ember-cli-deploy/pull/225) ([achambers](https://github.com/achambers)) 74 | - Add support for postBuildDeployHook [\#222](https://github.com/ember-cli/ember-cli-deploy/pull/222) ([ghedamat](https://github.com/ghedamat)) 75 | 76 | ### 0.5.0-beta.2 (June 14, 2015) 77 | 78 | #### Community Contributions 79 | 80 | - [#189](https://github.com/ember-cli/ember-cli-deploy/pull/189) [DOCUMENTATION] Upgrade instructions for plugin authors (and a bit for end users) [@duizendnegen/feature](https://github.com/duizendnegen/feature) 81 | - [#196](https://github.com/ember-cli/ember-cli-deploy/pull/196) [BUGFIX] Fix bug where fingerprint options in deploy.js don't work [@achambers](https://github.com/achambers) 82 | - [#197](https://github.com/ember-cli/ember-cli-deploy/pull/197) Remove unused code and commands deprecated in 0.4.0. [@yapplabs](https://github.com/yapplabs) 83 | - [#206](https://github.com/ember-cli/ember-cli-deploy/pull/206) Remove modifying fingerprint options on `included` hook [@achambers](https://github.com/achambers) 84 | - [#207](https://github.com/ember-cli/ember-cli-deploy/pull/207) Add 'prepare' hooks to pipeline [@achambers](https://github.com/achambers) 85 | - [#208](https://github.com/ember-cli/ember-cli-deploy/pull/208) [DOCUMENTATION] Update RFC with prepare hook [@achambers](https://github.com/achambers) 86 | - [#210](https://github.com/ember-cli/ember-cli-deploy/pull/210) add setup and teardown hooks to all commands [@ghedamat](https://github.com/ghedamat) 87 | - [#212](https://github.com/ember-cli/ember-cli-deploy/pull/212) [DOCUMENTATION] Update description of hooks in rfc [@ghedamat](https://github.com/ghedamat) 88 | - [#213](https://github.com/ember-cli/ember-cli-deploy/pull/213) Change deprecated require 'ember-cli/lib/errors/silent' to 'silent-error' [@dukex](https://github.com/dukex) 89 | 90 | ### 0.4.3 (July 12, 2015) 91 | 92 | This release fixes problems with the silent-error package used by 93 | `ember-cli-deploy` internally, brings improvements for the activate task and 94 | makes it possible to configure the build path that `ember-cli-deploy` uses to 95 | store files before uploading. 96 | 97 | Thanks to everyone who took time to contribute to this release! 98 | 99 | #### Community Contributions 100 | 101 | - [#156](https://github.com/ember-cli/ember-cli-deploy/pull/156) Fix `_materialize` using wrong this context [@jeffhertzler](https://github.com/jeffhertzler) 102 | - [#158](https://github.com/ember-cli/ember-cli-deploy/pull/158) added ember-cli-rest-index adapter to list of adapter [@leojh](https://github.com/leojh) 103 | - [#159](https://github.com/ember-cli/ember-cli-deploy/pull/159) Update video link in README. [@blimmer](https://github.com/blimmer) 104 | - [#161](https://github.com/ember-cli/ember-cli-deploy/pull/161) Remove leading '+' es from code of conduct [@pangratz](https://github.com/pangratz) 105 | - [#164](https://github.com/ember-cli/ember-cli-deploy/pull/164) [#151] read manifestPrefix from config [@pavloo](https://github.com/pavloo) 106 | - [#170](https://github.com/ember-cli/ember-cli-deploy/pull/170) Add ability to configure build paths, defaulting to tmp/deploy-dist/. [@duizendnegen/feature](https://github.com/duizendnegen/feature) 107 | - [#171](https://github.com/ember-cli/ember-cli-deploy/pull/171) Update ember cli 0.2.7 and fix npm warnings [@ghedamat](https://github.com/ghedamat) 108 | - [#175](https://github.com/ember-cli/ember-cli-deploy/pull/175) Manifest prefix for activate task. [@juggy](https://github.com/juggy) 109 | - [#176](https://github.com/ember-cli/ember-cli-deploy/pull/176) Use silent-error NPM Package [@jherdman](https://github.com/jherdman) 110 | - [#178](https://github.com/ember-cli/ember-cli-deploy/pull/178) Use SilentError to log errors when parsing config. [@ember-cli](https://github.com/ember-cli) 111 | 112 | Thank you to all who took the time to contribute! 113 | 114 | ### 0.4.2 (June 14, 2015) 115 | 116 | This release fixes asset upload issues with io.js, adds the possibility for 117 | index adapters to support multiple files and adds a configuration option to 118 | exclude asset files from deployment. 119 | 120 | #### Community Contributions 121 | 122 | - [#140](https://github.com/ember-cli/ember-cli-deploy/pull/140) Link to ember-deploy-couchbase. [@waltznetworks](https://github.com/waltznetworks) 123 | - [#113](https://github.com/ember-cli/ember-cli-deploy/pull/113) Provide better error support for missing environment config [@achambers](https://github.com/achambers) 124 | - [#115](https://github.com/ember-cli/ember-cli-deploy/pull/115) Changed package to be able to run tests on windows. [@Twinkletoes](https://github.com/Twinkletoes) 125 | - [#119](https://github.com/ember-cli/ember-cli-deploy/pull/119) Stub active, list, createTag UnknownAdapter methods [@waltznetworks](https://github.com/waltznetworks) 126 | - [#120](https://github.com/ember-cli/ember-cli-deploy/pull/120) [DOCUMENTATION] Make Sinatra example a bit more secure [@elucid](https://github.com/elucid) 127 | - [#124](https://github.com/ember-cli/ember-cli-deploy/pull/124) Index Adapter support for multiple files [@Ahalogy](https://github.com/Ahalogy) 128 | - [#128](https://github.com/ember-cli/ember-cli-deploy/pull/128) Link to custom adapters section for quick ref [@jayphelps](https://github.com/jayphelps) 129 | - [#129](https://github.com/ember-cli/ember-cli-deploy/pull/129) Make a callout easily actionable [@jorgedavila25](https://github.com/jorgedavila25) 130 | - [#141](https://github.com/ember-cli/ember-cli-deploy/pull/141) Add configuration option to exclude asset files from being deployed. [@yapplabs](https://github.com/yapplabs) 131 | - [#142](https://github.com/ember-cli/ember-cli-deploy/pull/142) Test against stable node versions [@yapplabs](https://github.com/yapplabs) 132 | - [#144](https://github.com/ember-cli/ember-cli-deploy/pull/144) Resolve JSHint error on deploy.js blueprint [@blimmer](https://github.com/blimmer) 133 | - [#146](https://github.com/ember-cli/ember-cli-deploy/pull/146) Make io.js work [@trym](https://github.com/trym) 134 | 135 | Thank you to all who took the time to contribute! 136 | 137 | ### 0.4.1 (March 13, 2015) 138 | 139 | This release mainly revolves round fixing a bug around `child_process` and `execSync` compatability among the nodejs versions and platforms. 140 | 141 | #### Community Contributions 142 | 143 | - [#93](https://github.com/ember-cli/ember-cli-deploy/pull/93) [BUGFIX] execSync compat issue #92 [@joebartels](https://github.com/joebartels) 144 | - [#100](https://github.com/ember-cli/ember-cli-deploy/pull/100) [DOCS] Update config around production-like environments [@Soliah](https://github.com/Soliah) 145 | 146 | ### 0.4.0 (March 07, 2015) 147 | 148 | This release marks the merge of `achambers/ember-cli-deploy`, `LevelBossMike/ember-deploy` and `tedconf/ember-cli-front-end-builds` into the official `ember-cli-deploy` 149 | 150 | If you are upgrading from `achambers/ember-cli-deploy v0.0.6`, please follow these [miragtion steps](https://github.com/ember-cli/ember-cli-deploy/blob/master/MIGRATION_STEPS.md); 151 | 152 | #### Community Contributions 153 | 154 | - [#33](https://github.com/ember-cli/ember-cli-deploy/pull/33) [DOCS] Link to new S3 Index Adapter [@pootsbook](https://github.com/pootsbook) 155 | - [#65](https://github.com/ember-cli/ember-cli-deploy/pull/65) [DOCS] Update CodeClimate batch. [@LevelbossMike](https://github.com/LevelbossMike) 156 | - [#35](https://github.com/ember-cli/ember-cli-deploy/pull/35) [ENHANCEMENT] Match ember-cli's build command aliases by supporting --prod and --dev [@jamesfid](https://github.com/jamesfid) 157 | - [#36](https://github.com/ember-cli/ember-cli-deploy/pull/36) [ENHANCEMENT] Allow custom config file via --deploy-config-file. [@yapplabs](https://github.com/yapplabs) 158 | - [#63](https://github.com/ember-cli/ember-cli-deploy/pull/63) [BUGFIX] Fix regression to the type of object that was being passed to assets adapters as “config”. [@yapplabs](https://github.com/yapplabs) 159 | - [#56](https://github.com/ember-cli/ember-cli-deploy/pull/56) [BREAKING ENHANCEMENT] Deprecated commands no longer needed. [@ember-cli](https://github.com/ember-cli) 160 | - [#40](https://github.com/ember-cli/ember-cli-deploy/pull/40) [BUGFIX] Removed erroneous conflict markers. [@jamesfid](https://github.com/jamesfid) 161 | - [#58](https://github.com/ember-cli/ember-cli-deploy/pull/58) [ENHANCEMENT] Add blueprint to auto generate config/deploy.js [@ember-cli](https://github.com/ember-cli) 162 | - [#57](https://github.com/ember-cli/ember-cli-deploy/pull/57) [DEPRECATION] Deprecate use of deploy.json in favor of config/deploy.js. Closes #51 [@yapplabs](https://github.com/yapplabs) 163 | - [#66](https://github.com/ember-cli/ember-cli-deploy/pull/66) [DOCS] Add note for fingerprint.prepend and staging envs. [@LevelbossMike](https://github.com/LevelbossMike) 164 | - [#74](https://github.com/ember-cli/ember-cli-deploy/pull/74) [BREAKING DEPRECATION] Revert Unsupported Commands back to Deprecated for 0.4.0 release [@danshultz](https://github.com/danshultz) 165 | - [#85](https://github.com/ember-cli/ember-cli-deploy/pull/85) [DEPRECATION] npm post install message for users of v0.0.6 [@achambers](https://github.com/achambers) 166 | 167 | ### 0.3.1 (February 08, 2015) 168 | 169 | - [#32](https://github.com/LevelbossMike/ember-deploy/pull/32) add support for execSync in node >= 0.11 [@kriswill](https://github.com/kriswill) 170 | 171 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* 172 | -------------------------------------------------------------------------------- /node-tests/unit/tasks/pipeline-test.js: -------------------------------------------------------------------------------- 1 | var Promise = require('ember-cli/lib/ext/promise'); 2 | var PipelineTask = require('../../../lib/tasks/pipeline'); 3 | var Pipeline = require('../../../lib/models/pipeline'); 4 | var expect = require('../../helpers/expect'); 5 | 6 | describe('PipelineTask', function() { 7 | var mockProject = {addons: [], root: process.cwd()}; 8 | var mockConfig = {}; 9 | var mockUi; 10 | beforeEach(function(){ 11 | mockUi = { write: function() {}, writeError: function() {} }; 12 | }); 13 | 14 | describe('creating and setting up a new instance', function() { 15 | it ('raises an error if project is not provided', function() { 16 | var fn = function() { 17 | new PipelineTask({}); 18 | }; 19 | 20 | expect(fn).to.throw('No project passed to pipeline task'); 21 | }); 22 | 23 | it ('raises an error if ui is not provided', function() { 24 | var fn = function() { 25 | new PipelineTask({ 26 | project: mockProject, 27 | config: mockConfig 28 | }); 29 | }; 30 | 31 | expect(fn).to.throw('No ui passed to pipeline task'); 32 | }); 33 | 34 | it('raises an error if deployTarget is not provided', function() { 35 | var fn = function() { 36 | new PipelineTask({ 37 | project: mockProject, 38 | ui: mockUi, 39 | config: mockConfig 40 | }); 41 | }; 42 | 43 | expect(fn).to.throw('You need to provide a deployTarget: `ember deploy production`'); 44 | }); 45 | 46 | it('raises an error if config is not provided', function() { 47 | var fn = function() { 48 | new PipelineTask({ 49 | project: mockProject, 50 | ui: mockUi, 51 | deployTarget: 'development' 52 | }); 53 | }; 54 | 55 | expect(fn).to.throw('No config passed to pipeline task'); 56 | }); 57 | 58 | describe('registering addons with the pipeline', function() { 59 | it('registers addons with ember-cli-deploy-plugin keyword', function() { 60 | var project = { 61 | name: function() {return 'test-project';}, 62 | root: process.cwd(), 63 | addons: [ 64 | { 65 | name: 'ember-cli-deploy-test-plugin', 66 | pkg: { 67 | keywords: [ 68 | 'ember-cli-deploy-plugin' 69 | ] 70 | }, 71 | createDeployPlugin: function() { 72 | return { 73 | name: 'test-plugin', 74 | willDeploy: function() {}, 75 | upload: function() {} 76 | }; 77 | } 78 | } 79 | ] 80 | }; 81 | 82 | var task = new PipelineTask({ 83 | project: project, 84 | ui: mockUi, 85 | deployTarget: 'development', 86 | config: mockConfig, 87 | hooks: ['willDeploy', 'upload'] 88 | }); 89 | task.setup(); 90 | var registeredHooks = task._pipeline._pipelineHooks; 91 | 92 | expect(registeredHooks.willDeploy[0].name).to.eq('test-plugin'); 93 | expect(registeredHooks.willDeploy[0].fn).to.be.a('function'); 94 | expect(registeredHooks.upload[0].name).to.eq('test-plugin'); 95 | expect(registeredHooks.upload[0].fn).to.be.a('function'); 96 | }); 97 | 98 | it('does not register addons missing the ember-cli-deploy-plugin keyword', function() { 99 | var project = { 100 | name: function() {return 'test-project';}, 101 | root: process.cwd(), 102 | addons: [ 103 | { 104 | name: 'ember-cli-deploy-test-plugin', 105 | pkg: { 106 | keywords: [ 107 | 'some-other-plugin' 108 | ] 109 | }, 110 | createDeployPlugin: function() { 111 | return { 112 | willDeploy: function() {}, 113 | upload: function() {} 114 | }; 115 | } 116 | } 117 | ] 118 | }; 119 | 120 | var task = new PipelineTask({ 121 | project: project, 122 | ui: mockUi, 123 | deployTarget: 'development', 124 | config: mockConfig, 125 | hooks: ['willDeploy', 'upload'] 126 | }); 127 | 128 | var registeredHooks = task._pipeline._pipelineHooks; 129 | 130 | expect(registeredHooks.willDeploy[0]).to.be.undefined; 131 | }); 132 | 133 | it('does not register addons that don\'t implement the createDeployPlugin function', function() { 134 | var project = { 135 | name: function() {return 'test-project';}, 136 | root: process.cwd(), 137 | addons: [ 138 | { 139 | name: 'ember-cli-deploy-test-plugin', 140 | pkg: { 141 | keywords: [ ] 142 | }, 143 | someOtherFunction: function() { 144 | return { 145 | willDeploy: function() {} 146 | }; 147 | } 148 | } 149 | ] 150 | }; 151 | 152 | var task = new PipelineTask({ 153 | project: project, 154 | ui: mockUi, 155 | deployTarget: 'development', 156 | config: mockConfig, 157 | hooks: ['willDeploy', 'upload'] 158 | }); 159 | 160 | var registeredHooks = task._pipeline._pipelineHooks; 161 | 162 | expect(registeredHooks.willDeploy[0]).to.be.undefined; 163 | }); 164 | 165 | it('registers configured addons only, if addons configuration is present', function () { 166 | var project = { 167 | name: function() {return 'test-project';}, 168 | root: process.cwd(), 169 | addons: [ 170 | { 171 | name: 'ember-cli-deploy-foo-plugin', 172 | pkg: { 173 | keywords: [ 174 | 'ember-cli-deploy-plugin' 175 | ] 176 | }, 177 | createDeployPlugin: function() { 178 | return { 179 | name: 'foo-plugin', 180 | willDeploy: function() {}, 181 | upload: function() {} 182 | }; 183 | } 184 | }, 185 | { 186 | name: 'ember-cli-deploy-bar-plugin', 187 | pkg: { 188 | keywords: [ 189 | 'ember-cli-deploy-plugin' 190 | ] 191 | }, 192 | createDeployPlugin: function() { 193 | return { 194 | name: 'bar-plugin', 195 | willDeploy: function() {}, 196 | upload: function() {} 197 | }; 198 | } 199 | } 200 | ] 201 | }; 202 | 203 | var task = new PipelineTask({ 204 | project: project, 205 | ui: mockUi, 206 | deployTarget: 'development', 207 | config: { plugins: ['foo-plugin'] }, 208 | hooks: ['willDeploy', 'upload'] 209 | }); 210 | task.setup(); 211 | var registeredHooks = task._pipeline._pipelineHooks; 212 | 213 | expect(registeredHooks.willDeploy.length).to.equal(1); 214 | expect(registeredHooks.willDeploy[0].name).to.eq('foo-plugin'); 215 | expect(registeredHooks.willDeploy[0].fn).to.be.a('function'); 216 | expect(registeredHooks.upload.length).to.equal(1); 217 | expect(registeredHooks.upload[0].name).to.eq('foo-plugin'); 218 | expect(registeredHooks.upload[0].fn).to.be.a('function'); 219 | }); 220 | it('registers dependent plugin addons of a plugin pack addon designated by the ember-cli-deploy-plugin-pack keyword', function() { 221 | var project = { 222 | name: function() {return 'test-project';}, 223 | root: process.cwd(), 224 | addons: [ 225 | { 226 | name: 'ember-cli-deploy-test-plugin-pack', 227 | pkg: { 228 | keywords: [ 229 | 'ember-cli-deploy-plugin-pack' 230 | ] 231 | }, 232 | addons: [ 233 | { 234 | name: 'ember-cli-deploy-test-plugin', 235 | pkg: { 236 | keywords: [ 237 | 'ember-cli-deploy-plugin' 238 | ] 239 | }, 240 | createDeployPlugin: function() { 241 | return { 242 | name: 'test-plugin', 243 | willDeploy: function() {}, 244 | upload: function() {} 245 | }; 246 | } 247 | } 248 | ] 249 | } 250 | ] 251 | }; 252 | 253 | var task = new PipelineTask({ 254 | project: project, 255 | ui: mockUi, 256 | deployTarget: 'development', 257 | config: mockConfig, 258 | hooks: ['willDeploy', 'upload'] 259 | }); 260 | task.setup(); 261 | var registeredHooks = task._pipeline._pipelineHooks; 262 | 263 | expect(registeredHooks.willDeploy.length).to.eq(1); 264 | expect(registeredHooks.willDeploy[0].name).to.eq('test-plugin'); 265 | expect(registeredHooks.willDeploy[0].fn).to.be.a('function'); 266 | expect(registeredHooks.upload.length).to.eq(1); 267 | expect(registeredHooks.upload[0].name).to.eq('test-plugin'); 268 | expect(registeredHooks.upload[0].fn).to.be.a('function'); 269 | }); 270 | }); 271 | }); 272 | 273 | describe('executing the pipeline task', function() { 274 | it ('executes the pipeline, passing in the deployment context', function() { 275 | var pipelineExecuted = false; 276 | var pipelineContext; 277 | 278 | var project = { 279 | name: function() {return 'test-project';}, 280 | root: process.cwd(), 281 | addons: [ ] 282 | }; 283 | 284 | var task = new PipelineTask({ 285 | project: project, 286 | ui: mockUi, 287 | deployTarget: 'development', 288 | config: { build: { environment: 'development' }}, 289 | commandOptions: {revision: '123abc'}, 290 | hooks: ['willDeploy', 'upload'], 291 | pipeline: { 292 | execute: function(context) { 293 | pipelineExecuted = true; 294 | pipelineContext = context; 295 | return Promise.resolve(); 296 | } 297 | } 298 | }); 299 | 300 | return expect(task.run()).to.be.fulfilled 301 | .then(function() { 302 | expect(pipelineExecuted).to.be.true; 303 | expect(pipelineContext.ui).to.eq(mockUi); 304 | expect(pipelineContext.project).to.eq(project); 305 | expect(pipelineContext.deployTarget).to.eq('development'); 306 | expect(pipelineContext.config.build.environment).to.eq('development'); 307 | expect(pipelineContext.commandOptions.revision).to.eq('123abc'); 308 | }); 309 | }); 310 | 311 | it('executes the pipeline, logging at a verbose level', function() { 312 | var logOutput = ""; 313 | mockUi = { 314 | verbose: true, 315 | write: function(s) { 316 | logOutput = logOutput + s; 317 | }, 318 | writeError: function(s) { 319 | logOutput = logOutput + s + "\n"; 320 | } 321 | }; 322 | var project = { 323 | name: function() {return 'test-project';}, 324 | root: process.cwd(), 325 | addons: [ 326 | { 327 | name: 'ember-cli-deploy-test-plugin', 328 | pkg: { 329 | keywords: [ 330 | 'ember-cli-deploy-plugin' 331 | ] 332 | }, 333 | createDeployPlugin: function() { 334 | return { 335 | name: 'test-plugin', 336 | willDeploy: function() { 337 | }, 338 | upload: function() {} 339 | }; 340 | } 341 | } 342 | ] 343 | }; 344 | 345 | var task = new PipelineTask({ 346 | project: project, 347 | ui: mockUi, 348 | deployTarget: 'development', 349 | config: mockConfig, 350 | commandOptions: {revision: '123abc'}, 351 | hooks: ['willDeploy', 'upload'], 352 | pipeline: new Pipeline(['willDeploy', 'upload'], { 353 | ui: mockUi 354 | }) 355 | }); 356 | 357 | task.setup(); 358 | return expect(task.run()).to.be.fulfilled.then(function() { 359 | var logLines = logOutput.split("\n"); 360 | expect(logLines[ 0]).to.eq("\u001b[34mRegistering hook -> willDeploy[test-plugin]"); 361 | expect(logLines[ 1]).to.eq("\u001b[39m\u001b[34mRegistering hook -> upload[test-plugin]"); 362 | expect(logLines[ 2]).to.eq("\u001b[39m\u001b[34mRegistering hook -> willDeploy[test-plugin]"); 363 | expect(logLines[ 3]).to.eq("\u001b[39m\u001b[34mRegistering hook -> upload[test-plugin]"); 364 | expect(logLines[ 4]).to.eq("\u001b[39m\u001b[34mExecuting pipeline"); 365 | expect(logLines[ 5]).to.eq("\u001b[39m\u001b[34m|"); 366 | expect(logLines[ 6]).to.eq("\u001b[39m\u001b[34m+- willDeploy"); 367 | expect(logLines[ 7]).to.eq("\u001b[39m\u001b[34m| |"); 368 | expect(logLines[ 8]).to.eq("\u001b[39m\u001b[34m| +- test-plugin"); 369 | expect(logLines[ 9]).to.eq("\u001b[39m\u001b[34m| |"); 370 | expect(logLines[10]).to.eq("\u001b[39m\u001b[34m| +- test-plugin"); 371 | expect(logLines[11]).to.eq("\u001b[39m\u001b[34m|"); 372 | expect(logLines[12]).to.eq("\u001b[39m\u001b[34m+- upload"); 373 | expect(logLines[13]).to.eq("\u001b[39m\u001b[34m| |"); 374 | expect(logLines[14]).to.eq("\u001b[39m\u001b[34m| +- test-plugin"); 375 | expect(logLines[15]).to.eq("\u001b[39m\u001b[34m| |"); 376 | expect(logLines[16]).to.eq("\u001b[39m\u001b[34m| +- test-plugin"); 377 | expect(logLines[17]).to.eq("\u001b[39m\u001b[34m|"); 378 | expect(logLines[18]).to.eq("\u001b[39m\u001b[34mPipeline complete"); 379 | }); 380 | }); 381 | }); 382 | 383 | describe('plugin aliases are correctly handled', function() { 384 | it('passes correct name to single plugin alias', function () { 385 | var correctAliasUsed = false; 386 | var project = { 387 | name: function() {return 'test-project';}, 388 | root: process.cwd(), 389 | addons: [ 390 | { 391 | name: 'ember-cli-deploy-foo-plugin', 392 | pkg: { 393 | keywords: [ 394 | 'ember-cli-deploy-plugin' 395 | ] 396 | }, 397 | createDeployPlugin: function(options) { 398 | correctAliasUsed = (options.name === 'bar-alias'); 399 | } 400 | } 401 | ] 402 | }; 403 | 404 | var task = new PipelineTask({ 405 | project: project, 406 | ui: mockUi, 407 | deployTarget: 'staging', 408 | config: { plugins: ['foo-plugin:bar-alias'] }, 409 | pipeline: { 410 | hookNames: function () { 411 | return []; 412 | } 413 | } 414 | }); 415 | 416 | task.setup(); 417 | expect(correctAliasUsed).to.be.true; 418 | }); 419 | 420 | it('passes correct name to multiple instances of the same plugin', function () { 421 | var correctAliasUsed = { 422 | 'foo-plugin': false, // plugin without alias 423 | 'bar-alias': false, // first alias 424 | 'doo-alias': false // second alias 425 | }; 426 | 427 | var project = { 428 | name: function() {return 'test-project';}, 429 | root: process.cwd(), 430 | addons: [ 431 | { 432 | name: 'ember-cli-deploy-foo-plugin', 433 | pkg: { 434 | keywords: [ 435 | 'ember-cli-deploy-plugin' 436 | ] 437 | }, 438 | createDeployPlugin: function(options) { 439 | correctAliasUsed[options.name] = true; 440 | } 441 | } 442 | ] 443 | }; 444 | 445 | var task = new PipelineTask({ 446 | project: project, 447 | ui: mockUi, 448 | deployTarget: 'staging', 449 | config: { plugins: ['foo-plugin', 'foo-plugin:bar-alias', 'foo-plugin:doo-alias'] }, 450 | pipeline: { 451 | hookNames: function () { 452 | return []; 453 | } 454 | } 455 | }); 456 | 457 | task.setup(); 458 | expect(correctAliasUsed['foo-plugin']).to.be.true; 459 | expect(correctAliasUsed['bar-alias']).to.be.true; 460 | expect(correctAliasUsed['doo-alias']).to.be.true; 461 | }); 462 | 463 | it('throws error on non-existent plugin in whitelist and appends them to err.unavailablePlugins', function () { 464 | var project = { 465 | name: function() {return 'test-project';}, 466 | root: process.cwd() 467 | }; 468 | 469 | var task = new PipelineTask({ 470 | project: project, 471 | ui: mockUi, 472 | deployTarget: 'production', 473 | config: { plugins: ['foo-plugin', 'foo-plugin:bar-alias', 'foo-plugin:doo-alias'] }, 474 | deployConfigPath: 'node-tests/fixtures/config/deploy-for-addons-config-test-with-aliases.js', 475 | }); 476 | 477 | try { 478 | task.setup(); 479 | expect(false).to.be.true; 480 | } catch(err) { 481 | expect(Object.keys(err.unavailablePlugins).length).to.equal(3); 482 | } 483 | }); 484 | }); 485 | }); 486 | --------------------------------------------------------------------------------