<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ryan J. Salva</title>
    <description>JavaScript Developer, Apache Cordova Committer, Visual Studio Program Manager, Public Speaker, CrossFitter, SciFi Connoisseur and Whiskey Enthusiast.</description>
    <link>http://ryanjsalva.com/</link>
    <atom:link href="http://ryanjsalva.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 16 May 2016 22:59:09 +0000</pubDate>
    <lastBuildDate>Mon, 16 May 2016 22:59:09 +0000</lastBuildDate>
    <generator>Jekyll v3.1.3</generator>
    
      <item>
        <title>Build the Ideal Cordova Dev Environment</title>
        <description>&lt;p&gt;Laptops are very personal, very private things. As the days and months pass by, little piles of messes accrue in the file system that are uniquely our own. At the same time, a developer’s laptop is also finely tuned for productivity. As someone who has developed quite a few mobile apps using &lt;a href=&quot;http://cordova.apache.org/&quot;&gt;Apache Cordova&lt;/a&gt;, I thought it might be fun to share some of the optimizations I’ve made to my laptop for Cordova development.&lt;/p&gt;

&lt;p&gt;I haven’t found the ideal dev environment yet – &lt;em&gt;I’m always tweaking things&lt;/em&gt; – but it’s a beautiful mess. This post describes how to build a good dev environment for cross-platform development targeting iOS, Android and Windows.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#hardware&quot;&gt;Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#nvm&quot;&gt;Node Version Manager (NVM)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#global&quot;&gt;Globally Installed NPM Packages&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#code&quot;&gt;VS Code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bash_profile&quot;&gt;.bash_profile&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#androidsdk&quot;&gt;Android SDK&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#emulator&quot;&gt;Android Emulator&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#parallels&quot;&gt;Parallels&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#vs&quot;&gt;Visual Studio&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#rba&quot;&gt;Remote Build Agent&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you’ve got suggestions for improvement, I would looooove to hear them. 💖 Share your tips in the comments section below.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;hardware&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;hardware&quot;&gt;Hardware&lt;/h2&gt;
&lt;p&gt;If you’re targeting iOS, you need a Mac somewhere in the equation. Likewise, if you’re targeting Windows, you need Windows. Until Apple changes their licensing, the only way for the two systems to co-exist on the same hardware is by running Parallels on a Mac device. So, that’s what I do.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Hardware:&lt;/strong&gt; 13” MacBook Pro with all the RAM and disk upgrades available.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Operating Systems:&lt;/strong&gt; Mac OSX El Capitan and &lt;a href=&quot;http://www.parallels.com/&quot;&gt;Parallels&lt;/a&gt; running Windows 10.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it’s not an option for you to develop on a Mac, there are plenty of cloud build solutions available (e.g. &lt;a href=&quot;https://build.phonegap.com/&quot;&gt;PhoneGap Build&lt;/a&gt;, &lt;a href=&quot;http://www.macincloud.com/&quot;&gt;MacInCloud&lt;/a&gt;, &lt;a href=&quot;http://www.macstadium.com&quot;&gt;Mac Stadium&lt;/a&gt;), but since this tutorial is focused on building a local development environment, we’ll leave that subject for another day.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;library&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;mobile-device-library-&quot;&gt;Mobile Device Library 📱&lt;/h3&gt;
&lt;p&gt;For local testing, I keep a library of devices and operating systems representative of the general population. My goal isn’t to test against everything, but to cover the “big players.”&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;iPhone 5&lt;/li&gt;
  &lt;li&gt;iPhone 6s&lt;/li&gt;
  &lt;li&gt;Samsung Galaxy S4&lt;/li&gt;
  &lt;li&gt;Nexus 7&lt;/li&gt;
  &lt;li&gt;Windows 950&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a name=&quot;nvm&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;node-version-manager-nvm&quot;&gt;Node Version Manager (NVM)&lt;/h2&gt;
&lt;p&gt;Besides Cordova itself, &lt;a href=&quot;http://nvm.sh&quot;&gt;Node Version Manager (NVM)&lt;/a&gt; is the single most important piece of software for you to install. Not all versions of Node and NPM are compatible with all versions of Cordova. In fact, it’s kind of a complicated mess:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Cordova&lt;/th&gt;
      &lt;th&gt;Node&lt;/th&gt;
      &lt;th&gt;NPM&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&amp;lt;5.3.3&lt;/td&gt;
      &lt;td&gt;^0.12.0&lt;/td&gt;
      &lt;td&gt;^2.0.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&amp;gt;=5.3.3&lt;/td&gt;
      &lt;td&gt;~4.0.0&lt;/td&gt;
      &lt;td&gt;^2.0.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&amp;gt;=5.4.1&lt;/td&gt;
      &lt;td&gt;^5.0.0&lt;/td&gt;
      &lt;td&gt;^3.0.0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;NVM allows you to install and manage multiple versions of Node + NPM simultaneously on the same machine. If you’re going to maintain multiple projects over time, NVM allows you to continue using Cordova 4.x with Node 0.12 on old maintenance projects, while using the latest-and-greatest versions of Node + NPM on your new projects.&lt;/p&gt;

&lt;p&gt;Also, because NVM installs packages in the user’s local directory where you have ownership rights, permission errors caused by installing NPM packages with &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo&lt;/code&gt; go away. For an eloquent explanation of why you should avoid using &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo&lt;/code&gt; with npm , &lt;a href=&quot;https://blog.explosionpills.com/dont-use-sudo-with-npm/&quot;&gt;read this post from Explosion Pills.&lt;/a&gt; The relevant part:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Using &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo npm install&lt;/code&gt; (and potentially &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo npm &amp;lt;anything&amp;gt;&lt;/code&gt;) is a &lt;em&gt;bad idea ™.&lt;/em&gt; This is an issue for at least a few reasons:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;npm install&lt;/code&gt; has the ability to run arbitrary scripts. Due to how npm is set up and the fact that you can alter the registry and it can use DNS, it is possible that you will accidentally install a malicious package in general, install a malicious package masquerading as a perfectly valid package, or install a package with good intentions that may run scripts that are somehow detrimental to your system if run as root.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Running &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo&lt;/code&gt; npm install (without -g) will create a local directory that can only be altered by the root user. This can really screw things up for you if you try to do &lt;code class=&quot;highlighter-rouge&quot;&gt;npm &amp;lt;something&amp;gt;&lt;/code&gt; in the same directory or project later on.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Even &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo npm install -g&lt;/code&gt; with a valid installation target can mess things up for you and make it hard to use npm without &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo&lt;/code&gt; under some circumstances in the future – particularly if you change your npmconfiguration midstream. The root user can and will create files in your npm cache and potentially a file like &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.npm/_locks&lt;/code&gt;, and future npm installor &lt;code class=&quot;highlighter-rouge&quot;&gt;npm install -g&lt;/code&gt; will give you the dreaded EACCES error.&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;NVM makes it so that you never have to use &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo&lt;/code&gt; again. Installing it is easy. Simply follow the instructions on the &lt;a href=&quot;http://nvm.sh&quot;&gt;NVM website&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# install nvm&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;curl&lt;/span&gt; -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash

&lt;span class=&quot;c1&quot;&gt;# confirm that nvm is installed&lt;/span&gt;
nvm --version

&lt;span class=&quot;c1&quot;&gt;# install the latest stable release&lt;/span&gt;
nvm install v4.4.4

&lt;span class=&quot;c1&quot;&gt;# make it the default&lt;/span&gt;
nvm &lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;default v4.4.4&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I recommend using the last stable version of Node for Cordova development. Remember that even (as opposed to odd) version numbers are officially stable and &lt;a href=&quot;https://github.com/nodejs/LTS&quot;&gt;“Long Term Supported.”&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;global&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;globally-installed-npm-packages&quot;&gt;Globally Installed NPM Packages&lt;/h2&gt;
&lt;p&gt;When building a new machine, there are a few utilities that I expect to use in (almost) every project. Rather than install a duplicate instance in each project directory, I prefer to install these npm packages globally:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Package Name&lt;/th&gt;
      &lt;th&gt;Why?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;http://cordova.apache.org/&quot;&gt;cordova&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;This is a Cordova development environment, right? Required.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/ionic&quot;&gt;ionic&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Ionic delivers silky smooth, good-looking UI and provides a good starting point for most projects.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/taco-cli&quot;&gt;taco-cli&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;TACO provides an array of utilities for Cordova development, including &lt;code class=&quot;highlighter-rouge&quot;&gt;--livereload&lt;/code&gt; which auto-refeshes the webview on save for browsers, emulators and devices&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/code-push-cli&quot;&gt;code-push-CLI&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Code Push is my preferred service for publishing updates without re-submitting  to the app store.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/typescript&quot;&gt;typescript&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;TypeScript provides a much better code editing experience than JavaScript alone with auto-complete, code refactoring and access to future JS features (e.g. decorators) that aren’t available in ES6 today. It’s also recommended by the teams building Ionic and Angular.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/eslint&quot;&gt;eslint&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Eslint will help you enforce a sensible coding style guide without being a jerk about it.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/eslint-plugin-standard&quot;&gt;eslint-plugin-standard&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;This plugin forms the foundation of our coding style guide.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/git&quot;&gt;git&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Source control is kinda necessary. Required.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/gulp&quot;&gt;gulp&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Gulp is a build task manager. It might be a little old school, but it’s simple and gets the job done. You might prefer Browserify or Web Pack.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/ios-deploy&quot;&gt;ios-deploy&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Enables ios-build.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/ios-sim&quot;&gt;ios-sim&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Enables launch to the ios simulator without opening xcode&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.npmjs.com/package/remotebuild&quot;&gt;remotebuild&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;RemoteBuild is a simple node server that allows your Windows VM to delegate iOS builds to Xcode and launch the simulator.  In other words, it allows you to build for iOS from your Windows machine.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;You can install all of them at once by executing:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;npm install -g cordova ionic code-push-cli typescript eslint eslint-plugin-standard git gulp ios-deploy ios-sim remotebulid&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a name=&quot;code&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;vs-code&quot;&gt;VS Code&lt;/h2&gt;
&lt;p&gt;I use &lt;a href=&quot;http://code.visualstudio.com&quot;&gt;VS Code&lt;/a&gt; as my primary editor. It’s based on Atom, but don’t be fooled into thinking it’s an Atom clone. Besides being significantly faster – especially with large projects – Code has a number of advantages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Large ecosystem of plugins, extensions and themes&lt;/li&gt;
  &lt;li&gt;Git integration&lt;/li&gt;
  &lt;li&gt;Built-in debugger&lt;/li&gt;
  &lt;li&gt;Wicked good intellisense and code refactoring features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-vs-code.png&quot; alt=&quot;Debugging in VS Code&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To the default installation, I add the following extensions:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Extension&lt;/th&gt;
      &lt;th&gt;Why?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vsmobile.cordova-tools&quot;&gt;Cordova-tools&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Adds both Cordova and Ionic CLI integration, debugging tools for iOS/Android, intellisense for plugins and code snippets for Ionic UI controls.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint&quot;&gt;ESLint&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;This plugin leverages the ESLint npm packages installed above. Style violations receive a red squiggly in the code editor. You can right-click on any of these errors and auto-fix all the errors in your project.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=eg2.tslint&quot;&gt;TSLint&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;TSLint provides the same features offered by ESLint, but for TypeScript rather than JavaScript.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;To install these extensions, simply invoke the Command Pallette with Command+P (Mac) or Control+P (Windows) and type:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-console&quot; data-lang=&quot;console&quot;&gt;&amp;gt; ext install cordova-tools
&amp;gt; ext install eslint
&amp;gt; ext install tslint&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;edit-preview-and-debug-loop&quot;&gt;Edit, Preview and Debug Loop&lt;/h3&gt;
&lt;p&gt;Like most developers, the vast majority of my time is spent in a quick, iterative loop where I progressively (1) edit code, (2) refresh the webview, then (3) debug and inspect the result. There are lots of tricks intended to shortcut steps within this loop – mostly by auto-refreshing the webview on save.&lt;/p&gt;

&lt;p class=&quot;center max-width-50&quot;&gt;&lt;img src=&quot;/assets/2016-05-15-edit-preview-debug.png&quot; alt=&quot;Edit, Preview, Debug&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I accomplish this today using either &lt;code class=&quot;highlighter-rouge&quot;&gt;taco run ios --livereload&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;ionic serve --lab&lt;/code&gt; depending on the circumstances.&lt;/p&gt;

&lt;p&gt;Generally speaking, I use &lt;code class=&quot;highlighter-rouge&quot;&gt;taco run [platfrom] --livereload&lt;/code&gt; any time I want to deploy to an emulator or device where (a) all the plugins work and (b) fidelity is high. Meanwhile, I reserve &lt;code class=&quot;highlighter-rouge&quot;&gt;ionic serve --lab&lt;/code&gt; for those situations where I merely want to work quickly on a desktop browser and don’t need plugins or perfect simulation.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;bash_profile&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;bashprofile&quot;&gt;.bash_profile&lt;/h2&gt;
&lt;p&gt;So much magic happens in the terminal. It’s a shame that it’s so damn ugly and illegible by default. A lot of people choose &lt;a href=&quot;http://ohmyz.sh/&quot;&gt;oh-my-zsh&lt;/a&gt;, but I’m quite fond of my custom .bash_profile assembled from a handful of online tutorials and pimped out to match my personal tastes. You can find my .bash_profile here: &lt;a href=&quot;http://github.com/ryanjsalva/bash&quot;&gt;http://github.com/ryanjsalva/bash&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The basic strategy is to declare nothing directly in .bash_profile, but instead break the file into multiple modules which I import. You can learn a lot by just looking at the code, but I’ll point out a few important features below.&lt;/p&gt;

&lt;h3 id=&quot;bashnvm&quot;&gt;.bash_nvm&lt;/h3&gt;
&lt;p&gt;This file is required to use NVM, but it also adds command completion to your terminal. To see completion in action, type “nvm” in the terminal, then tab to complete partial words or double-tab between words to get a list of all the actions &amp;amp; arguments available.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/assets/2016-05-15-nvm-completion.gif&quot; alt=&quot;Command completion for NVM&quot; /&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# export NVM directory&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NVM_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.nvm&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# enable command completion&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; -r &lt;span class=&quot;nv&quot;&gt;$NVM_DIR&lt;/span&gt;/bash_completion &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; . &lt;span class=&quot;nv&quot;&gt;$NVM_DIR&lt;/span&gt;/bash_completion

&lt;span class=&quot;c1&quot;&gt;# source NVM&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; -s &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NVM_DIR&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/nvm.sh&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; . &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NVM_DIR&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/nvm.sh&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;bashfinder&quot;&gt;.bash_finder&lt;/h3&gt;
&lt;p&gt;This file is totally for the “power user.” 💪 It defines shortcuts for common terminal tasks. For example, these lines provide an easy way to show/hide all the hidden system dot files like .gitignore&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Show hidden files in Finder&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;showFiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;defaults write com.apple.finder AppleShowAllFiles YES; killall Finder /System/Library/CoreServices/Finder.app&#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#   Hide hidden files in Finder&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;hideFiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;defaults write com.apple.finder AppleShowAllFiles NO; killall Finder /System/Library/CoreServices/Finder.app&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I also include several shortcuts to make Terminal navigation easier:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Opens current directory in MacOS Finder&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;open -a Finder ./&#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#   &#39;cd&#39; to frontmost window of MacOS Finder&lt;/span&gt;
cdf &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;currFolderPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt; /usr/bin/osascript &lt;span class=&quot;sh&quot;&gt;&amp;lt;&amp;lt;EOT
        tell application &quot;Finder&quot;
            try
        set currFolder to (folder of the front window as alias)
            on error
        set currFolder to (path to desktop folder as alias)
            end try
            POSIX path of currFolder
        end tell
EOT
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cd to &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$currFolderPath&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$currFolderPath&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Always list files/folders after cd&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command cd&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;; ll; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Go Home&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias&lt;/span&gt; ~&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cd ~&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Clear terminal display&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;clear&#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Find executables&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;which&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;type -all&#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# After you create a directory, cd into it&lt;/span&gt;
mkdir&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;mkdir -p &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;; &lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Move a file to the MacOS trash&lt;/span&gt;
trash &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command mv&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; ~/.Trash; ll; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Full Recursive Directory Listing&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;ls -R | grep &quot;:$&quot; | sed -e &#39;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&#39;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;s/:$//&#39;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&#39;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39; -e &#39;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&#39;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;s/[^-][^\/]*\//--/g&#39;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&#39;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39; -e &#39;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&#39;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;s/^/   /&#39;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&#39;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39; -e &#39;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&#39;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;s/-/|/&#39;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&#39;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39; | less&#39;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create a ZIP archive of a folder&lt;/span&gt;
zipf &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; zip -r &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;.zip &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; ; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You’ll find many more shortcuts defined in my &lt;a href=&quot;http://github.com/ryanjsalva/bash&quot;&gt;GitHub repository&lt;/a&gt;, so be sure to look there for inspiration.&lt;/p&gt;

&lt;h3 id=&quot;bashcordova&quot;&gt;.bash_cordova&lt;/h3&gt;
&lt;p&gt;This little gem comes from the Apache Cordova project and provides completion for Cordova CLI. Tab to complete partial words or double-tab between words to get a list of all the actions &amp;amp; arguments available.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/assets/2016-05-15-cordova-completion.gif&quot; alt=&quot;Command completion for Cordova&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;bashnetwork&quot;&gt;.bash_network&lt;/h3&gt;
&lt;p&gt;Again, this is for the power user and helps you get information about your local network.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Show all open TCP/IP sockets&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;netCons&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;lsof -i&#39;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Flush out the DNS Cache&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;flushDNS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;dscacheutil -flushcache&#39;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Display open sockets&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lsock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;sudo /usr/sbin/lsof -i -P&#39;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Display only open UDP sockets&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lsockU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;sudo /usr/sbin/lsof -nP | grep UDP&#39;&lt;/span&gt; 

&lt;span class=&quot;c&quot;&gt;# Display only open TCP sockets&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lsockT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;sudo /usr/sbin/lsof -nP | grep TCP&#39;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Get info on connections for en0&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ipInfo0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;ipconfig getpacket en0&#39;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Get info on connections for en1&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ipInfo1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;ipconfig getpacket en1&#39;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# All listening connections&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;openPorts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;sudo lsof -i | grep LISTEN&#39;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# All ipfw rules inc/ blocked IPs&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;showBlocked&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;sudo ipfw list&#39;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# My external ip address&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;myip&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;dig +short myip.opendns.com @resolver1.opendns.com&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# My local/internal ip address&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;localip&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ipconfig getifaddr en0&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;bashprompt&quot;&gt;.bash_prompt&lt;/h3&gt;
&lt;p&gt;This prompt makes me happy. 😸 It does four things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Adds color&lt;/li&gt;
  &lt;li&gt;Identifies the current version of Node (super-important when using NVM)&lt;/li&gt;
  &lt;li&gt;Identifies the current branch when navigating a directory under source control&lt;/li&gt;
  &lt;li&gt;Adds emoji flare to make the command line a little more rock ‘n roll&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;my_prompt&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;NODEV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;node --version&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;NVMV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[node:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NODEV&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]&quot;&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;GIT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;&#39;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;BRANCH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;git branch 2&amp;gt; /dev/null | grep &lt;span class=&quot;se&quot;&gt;\*&lt;/span&gt; | awk &lt;span class=&quot;s1&quot;&gt;&#39;{print $2}&#39;&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$BRANCH&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; !&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GIT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[git:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$BRANCH&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]&quot;&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;fi
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$txtred$PWD&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$txtwht$NVMV&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GIT$txtrst&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;PROMPT_COMMAND&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;my_prompt
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PS1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;⚡ &quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a name=&quot;androidsdk&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;android-sdk&quot;&gt;Android SDK&lt;/h2&gt;
&lt;p&gt;It goes without saying that if you want to build for Android, then you need the Android SDK. Unless you want develop custom plugins, you don’t need Android Studio. Instead, keep it “light” and &lt;a href=&quot;http://developer.android.com/sdk/index.html#downloads&quot;&gt;just install the Command Line Tools (a.k.a. the SDK).&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you’ve installed the Android command line tools, go to your Terminal and open the SDK package manager by typing &lt;code class=&quot;highlighter-rouge&quot;&gt;android&lt;/code&gt;. Google encourages you to download and install a bunch of stuff you don’t need. Instead, uncheck everything and only select the following options:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-android-sdk.png&quot; alt=&quot;Android SDK options&quot; /&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Package&lt;/th&gt;
      &lt;th&gt;Why?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Tools (everything)&lt;/td&gt;
      &lt;td&gt;These are your build tools. If you want to build an APK, you’ll need everything here.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Android [version] (API [number])&lt;/td&gt;
      &lt;td&gt;SDK Platform&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Pay particular attention to the last option in the list: &lt;a href=&quot;https://software.intel.com/en-us/android/articles/intel-hardware-accelerated-execution-manager&quot;&gt;Intel x86 Emulator Accelerator (HAXM Installer)&lt;/a&gt;.  This option is critical because the Google emulator is slooooooow. 🐌 Unless you want to pay $400+ per year for the &lt;a href=&quot;https://www.genymotion.com/pricing-and-licensing/&quot;&gt;Genymotion&lt;/a&gt; emulator on your Mac, HAXM is your best option to improve Android emulator performance. But it requires an extra step to complete installation.&lt;/p&gt;

&lt;p&gt;After you’ve installed all the API packages, open &lt;strong&gt;~/Library/Android/android-sdk-macosx/extras/intel/Hardware_Accelerated_Execution_Manager/IntelHAXM_6.0.1.dmg&lt;/strong&gt; to finish installing HAXM. Follow the installation instructions accepting all the defaults, then you’ll be ready to start your first emulator.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;emulator&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;android-emulator&quot;&gt;Android Emulator&lt;/h2&gt;
&lt;p&gt;Whenever possible, I use a tethered device because they are faster and of higher fidelity. But if I don’t have a device handy or need to test on a version of the OS that’s not in my device library, then to-the-emulator I go.&lt;/p&gt;

&lt;p&gt;Google’s Android emulator doesn’t come with any pre-configured devices, so you have to create one first. From the terminal, run &lt;code class=&quot;highlighter-rouge&quot;&gt;android avd&lt;/code&gt; to open the Android Virtual Device Manager.  Under the “Android Virtual Devices” tab, click “Create” and configure your first device as shown below.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-emulator.png&quot; alt=&quot;Edit Android Virtual Device&quot; /&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Setting&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Why?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;AVD Name&lt;/td&gt;
      &lt;td&gt;Nexus-7&lt;/td&gt;
      &lt;td&gt;Just pick something short and descriptive&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Device&lt;/td&gt;
      &lt;td&gt;Nexus 7&lt;/td&gt;
      &lt;td&gt;This is a good catch-all device representative of tablets.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Target&lt;/td&gt;
      &lt;td&gt;Android 6.0 - API Level 23&lt;/td&gt;
      &lt;td&gt;A recent version of the OS. You’ll want to create emulators for older versions, too.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPU/ABI&lt;/td&gt;
      &lt;td&gt;Google APIs Intel Atom&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Keyboard&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Skin&lt;/td&gt;
      &lt;td&gt;Skin with dynamic hardware controls&lt;/td&gt;
      &lt;td&gt;You can use the controls to change device orientation, “shake” the device, click the home button, etc.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Front Camera&lt;/td&gt;
      &lt;td&gt;Webcam&lt;/td&gt;
      &lt;td&gt;I prefer to use my laptop’s webcam over the &lt;a href=&quot;https://www.google.com/search?q=max%20headroom&amp;amp;rct=j&quot;&gt;Max Headroom&lt;/a&gt; floating ball.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Memory Options&lt;/td&gt;
      &lt;td&gt;RAM: 1024, VM Heap: 32&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Internal Storage&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SD Card&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Emulation Options&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Once you’re done configuring, you can go back to the terminal and launch your emulator with:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# start the emulator&lt;/span&gt;
emulator @Nexus-7 &amp;amp;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt; at the end of that command means, “open in the background.” Observe that the emulator… well, it’s not &lt;em&gt;fast&lt;/em&gt;, but it’s not horrifically slow because Intel’s HAXM is speeding things up:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-haxm.png&quot; alt=&quot;Emulator launching with HAXM in the Terminal&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;other-apps-on-the-mac&quot;&gt;Other Apps on the Mac&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;App&lt;/th&gt;
      &lt;th&gt;Why?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://developer.apple.com/xcode/downloads/&quot;&gt;Xcode&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Build tools for iOS&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;http://www.adobe.com/photoshop&quot;&gt;PhotoShop&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Graphic Design (bitmap)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;http://www.adobe.com/illustrator&quot;&gt;Illustrator&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Graphic Design (vector)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://itunes.apple.com/us/app/helium/id1054607607?mt=12&quot;&gt;Hellium&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;For when I want to have a video playing while I work&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.mamp.info/en/&quot;&gt;MAMP&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Local server for rare occasion when I’m coding PHP&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://slack.com/&quot;&gt;Slack&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Team collaboration&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;a name=&quot;parallels&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;configuring-parallels&quot;&gt;Configuring Parallels&lt;/h2&gt;
&lt;p&gt;Once you’ve setup your Mac, the hard part is over. Windows is surprisingly easy to install and configure. When setting up your Windows virtual machine, Parallels will give you the option to optimize for “Software Development.”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-software-development.png&quot; alt=&quot;Optimize Windows 10 for Software Development&quot; /&gt;&lt;/p&gt;

&lt;p&gt;While you can preserve many of the defaults for this preset, you may want to make a few tweaks. I’m not going to provide an exhaustive list of every setting since many merely depend on personal preference, but I’ll highlight a few of the important ones:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Tab&lt;/th&gt;
      &lt;th&gt;Category&lt;/th&gt;
      &lt;th&gt;Name&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Why&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Options&lt;/td&gt;
      &lt;td&gt;Optimization&lt;/td&gt;
      &lt;td&gt;Performance&lt;/td&gt;
      &lt;td&gt;Faster virtual machine&lt;/td&gt;
      &lt;td&gt;Your VM will always be the process starved for resources&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Options&lt;/td&gt;
      &lt;td&gt;Optimization&lt;/td&gt;
      &lt;td&gt;Enable Hypervisor&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;Speeds up emulators running inside the VM&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Options&lt;/td&gt;
      &lt;td&gt;Optimization&lt;/td&gt;
      &lt;td&gt;Tune Windows for Speed&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;Speed is more important than fancy animations&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Options&lt;/td&gt;
      &lt;td&gt;Full Screen&lt;/td&gt;
      &lt;td&gt;Use OS X Full Screen&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;It took me a while to figure out how to get the resolution right, so I’m including it here.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Options&lt;/td&gt;
      &lt;td&gt;Full Screen&lt;/td&gt;
      &lt;td&gt;Allows Windows 10 to set display gamma&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;See above&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hardware&lt;/td&gt;
      &lt;td&gt;CPU &amp;amp; Memory&lt;/td&gt;
      &lt;td&gt;Processors&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Half&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;As a general rule, allocate half of your available processors to the VM&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hardware&lt;/td&gt;
      &lt;td&gt;CPU &amp;amp; Memory&lt;/td&gt;
      &lt;td&gt;Memory&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Half&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;As a general rule, allocate half of your available RAM to the VM&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hardware&lt;/td&gt;
      &lt;td&gt;Graphics&lt;/td&gt;
      &lt;td&gt;Graphics Memory&lt;/td&gt;
      &lt;td&gt;&lt;em&gt;Quarter&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt;As a general rule, allocate one quarter of your available GPU to the VM&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hardware&lt;/td&gt;
      &lt;td&gt;Graphics&lt;/td&gt;
      &lt;td&gt;Retina Resolution&lt;/td&gt;
      &lt;td&gt;Best for Retina&lt;/td&gt;
      &lt;td&gt;It just looks better/crisper&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hardware&lt;/td&gt;
      &lt;td&gt;Graphics&lt;/td&gt;
      &lt;td&gt;3D acceleration&lt;/td&gt;
      &lt;td&gt;DirectX10&lt;/td&gt;
      &lt;td&gt;At least on my machine, this does some weird magic that makes full screen look “right”&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hardware&lt;/td&gt;
      &lt;td&gt;Network&lt;/td&gt;
      &lt;td&gt;Source&lt;/td&gt;
      &lt;td&gt;Shared Network&lt;/td&gt;
      &lt;td&gt;Makes it possible to delegate builds to Xcode and VS from either side of the machine&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-cpu.png&quot; alt=&quot;Parallels configuration for CPU and Memory&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-full-screen.png&quot; alt=&quot;Parallels configuration for Full Screen&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-graphics.png&quot; alt=&quot;Parallels configuration for Graphics&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-network.png&quot; alt=&quot;Parallels configuration for Network&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-15-optimization.png&quot; alt=&quot;Parallels configuration for Optimization&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;vs&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;visual-studio&quot;&gt;Visual Studio&lt;/h2&gt;
&lt;p&gt;Once you’ve got Windows 10 up-and-running, you’ll want to install Visual Studio which includes all the build tools for Windows Universal Apps. &lt;a href=&quot;https://www.visualstudio.com&quot;&gt;Visual Studio 2015 Community Edition&lt;/a&gt; is “free for individual developers, open source projects, academic research, training, education, and small professional teams.” It also includes the &lt;a href=&quot;http://taco.visualstudio.com&quot;&gt;Tools for Apache Cordova (a.k.a. “TACO”)&lt;/a&gt;. Full Disclosure: I’m the product manager for TACO, so… yah, they’re kinda awesome.&lt;/p&gt;

&lt;p class=&quot;max-width-50 center shadow&quot;&gt;&lt;img src=&quot;/assets/2016-05-15-vs-setup.png&quot; alt=&quot;Visual Studio 2015 Setup&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;pull-quote&quot;&gt;“Free for individual developers, open source projects, academic research, training, education, and small professional teams.”&lt;/p&gt;

&lt;p&gt;To install TACO, select it from the optional components during VS setup. If you already have Visual Studio installed, you can add TACO by going to “Change or Remove Programs” in the Control Panel, right-clicking on Visual Studio and selecting “Change.”&lt;/p&gt;

&lt;p&gt;Visual Studio will install all the Windows and Android SDK build tools in your Parallels instance, so all the setup work you had to do on the Mac… it’s not necessary for Windows.&lt;/p&gt;

&lt;h3 id=&quot;local-vs-global&quot;&gt;Local vs. Global&lt;/h3&gt;
&lt;p&gt;By default, VS installs a “sandboxed” version of Node + NPM similar to the one used by NVM on your Mac. As of this writing, Visual Studio installs Node 0.12 and NPM 2.2 because they are universally compatible with all versions of Cordova &amp;lt;=6.x. While I recommend using the sandboxed version, you can use the globally installed versions of Node + NPM by changing the settings in &lt;strong&gt;Tools &amp;gt; Options.&lt;/strong&gt;&lt;/p&gt;

&lt;p class=&quot;max-width-75 center shadow&quot;&gt;&lt;img src=&quot;/assets/2016-05-15-sandboxed.png&quot; alt=&quot;Use the global version of node&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;rba&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;remote-build-agent&quot;&gt;Remote Build Agent&lt;/h2&gt;
&lt;p&gt;Code on your Mac should be accessible to Windows and vice versa so long as Parallels is running. You can build from the command line or use Visual Studio’s built-in F5 launcher. If you want to build from Visual Studio, but deploy to an iOS simulator or device, you’ll need to setup a remote build agent.&lt;/p&gt;

&lt;p&gt;In essence, the remote build agent is a light-weight node server that runs on your Mac and listens for builds from Visual Studio. When it receives a build request, it marshalls the request to Xcode. Upon finishing the build, it can either return the IPA to Visual Studio or deploy it to a Simulator/Device.&lt;/p&gt;

&lt;h2 id=&quot;share-with-others&quot;&gt;Share with Others&lt;/h2&gt;
&lt;p&gt;This isn’t entirely relevant to setting up a local development environment, but if you ever need to share your screen with others (e.g. during a virtual meeting or while presenting on-stage), then there are two other essential tools.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.vysor.io/&quot;&gt;Vysor.io&lt;/a&gt; screencasts your tethered Android device&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.google.com/webhp?q=screencast+iphone+with+quicktime&quot;&gt;Quicktime&lt;/a&gt; screencasts your tethered iOS device&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;celebrate-&quot;&gt;Celebrate! 🎉&lt;/h3&gt;
&lt;p&gt;You’ve reached the end of another long blog post. Celebrate your victory with another song in the playlist. I recommend selecting a classic from the archives, &lt;a href=&quot;https://www.youtube.com/watch?v=WzVLD6J_O8E&quot;&gt;“F**k and Run” by Liz Phair.&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Sun, 15 May 2016 00:00:00 +0000</pubDate>
        <link>http://ryanjsalva.com/2016/05/15/cordova-dev-environment.html</link>
        <guid isPermaLink="true">http://ryanjsalva.com/2016/05/15/cordova-dev-environment.html</guid>
        
        <category>cordova</category>
        
        <category>node</category>
        
        <category>nvm</category>
        
        <category>npm</category>
        
        <category>bash</category>
        
        <category>android</category>
        
        <category>ios</category>
        
        <category>windows</category>
        
        <category>xcode</category>
        
        <category>visualstudio</category>
        
        <category>vscode</category>
        
        
      </item>
    
      <item>
        <title>Maintain Separate Configs for Dev, Beta and Release Build Targets</title>
        <description>&lt;p&gt;&lt;strong&gt;This is part four of a four part series (😆 🎉 👏) and assumes you’ve already set-up continuous integration with our &lt;a href=&quot;http://github.com/ryanjsalva/superfly&quot;&gt;sample project&lt;/a&gt;. If you haven’t already, I highly recommend reading part one, &lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;Continuous Integration for Cordova Apps&lt;/a&gt;, where we setup the build server used in this tutorial.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;change-your-apps-configuration-based-on-branch&quot;&gt;Change Your App’s Configuration Based on Branch&lt;/h2&gt;
&lt;p&gt;In parts I - III, we used &lt;a href=&quot;https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx&quot;&gt;Visual Studio Team Services (VSTS)&lt;/a&gt; to setup build definitions for &lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;unit testing&lt;/a&gt;, &lt;a href=&quot;/2016/04/26/beta-testing-with-hockeyapp.html&quot;&gt;beta testing&lt;/a&gt; and &lt;a href=&quot;/2016/05/01/publish-without-resubmitting-to-the-app-store.html&quot;&gt;over-the-air updates&lt;/a&gt;. All three of our build definitions run using continuous integration on a single branch (i.e. the master branch). Consequently, every time we push a code change, all three of these build definitions run.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Android-Dev:&lt;/strong&gt; runs Karma+Jasmine unit tests to ensure code quality&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Android-Beta:&lt;/strong&gt; distributes the app to manual testers via HockeyApp&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Android-Release:&lt;/strong&gt; distributes app updates to the public via Code Push&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Frankly, this is bad. We don’t want to distribute a beta release every time there’s a feature check-in and we sure as hell don’t want to release in-development features to the public. To control what happens at check-in, we need a branching strategy. We need to run a different set of build steps depending on which branch receives the commit.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-07-code-flow.png&quot; alt=&quot;Cascading code flow and releases&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;two-big-advantages&quot;&gt;Two Big Advantages&lt;/h3&gt;

&lt;p&gt;By creating a unique branch for each release type, we gain two big advantages.&lt;/p&gt;

&lt;p&gt;First, we can &lt;strong&gt;maintain a different set of config files for each branch&lt;/strong&gt; – enabling us to use different API keys or service providers for each release type. For example, imagine that you want to use the “Staging” Code Push deployment key during development, but the “Production” deployment key when you release to the public. Using this technique, you can dynamically switch the keys during continuous integration.&lt;/p&gt;

&lt;p&gt;Second, we can &lt;strong&gt;trigger different build steps for each branch.&lt;/strong&gt; For example, we can run unit tests every time we push to the “dev” branch without actually releasing anything. When we’re ready for user feedback, we can release to beta testers via HockeyApp by pushing to the “beta” branch.&lt;/p&gt;

&lt;p&gt;To realize our dream, we must:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Update the Source Code&lt;/strong&gt;
    &lt;ol&gt;
      &lt;li&gt;Create a branch for each build definition&lt;/li&gt;
      &lt;li&gt;Create config files for each build definition&lt;/li&gt;
      &lt;li&gt;Add gulp tasks to move and rename the config files before build&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Configure VSTS&lt;/strong&gt;
    &lt;ol&gt;
      &lt;li&gt;Change each build definition to trigger based on branch&lt;/li&gt;
      &lt;li&gt;Source the build from the appropriate repository&lt;/li&gt;
      &lt;li&gt;Add a custom gulp build step within each build definition&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;update-the-source-code&quot;&gt;Update the Source Code&lt;/h2&gt;
&lt;p&gt;Let’s start by creating three new branches at the terminal:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# create a dev branch to trigger CI unit tests&lt;/span&gt;
git checkout -b dev
git commit &lt;span class=&quot;s2&quot;&gt;&quot;create dev branch&quot;&lt;/span&gt;
git push

&lt;span class=&quot;c1&quot;&gt;# create a beta branch to trigger CI hockeyapp distribution&lt;/span&gt;
git checkout -b beta
git commit &lt;span class=&quot;s2&quot;&gt;&quot;create beta branch&quot;&lt;/span&gt;
git push

&lt;span class=&quot;c1&quot;&gt;# create a release branch to trigger CI codepush distribution&lt;/span&gt;
git checkout -b release
git commit &lt;span class=&quot;s2&quot;&gt;&quot;create release branch&quot;&lt;/span&gt;
git push

&lt;span class=&quot;c1&quot;&gt;# respect the proper code flow; start in the dev branch&lt;/span&gt;
git checkout dev&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Right now, all three branches are a carbon copy of the master branch. Each branch can operate independently, but ideally the code should flow from dev to beta to release. Thus, we start by making all our changes in the dev branch.&lt;/p&gt;

&lt;h3 id=&quot;give-each-branch-a-custom-configxml&quot;&gt;Give each branch a custom config.xml&lt;/h3&gt;
&lt;p&gt;In a “normal” local development environment, Cordova build depends on a &lt;strong&gt;config.xml&lt;/strong&gt; file in the root directory to define things like icons, splash screens and the App ID. To support different configurations for each branch, I’ve created a special &lt;strong&gt;/config&lt;/strong&gt; folder with XML and JS files for each release type (i.e. dev, beta and release). Let’s compare a few lines from &lt;strong&gt;dev.xml&lt;/strong&gt; and &lt;strong&gt;beta.xml&lt;/strong&gt; to see what’s different:&lt;/p&gt;

&lt;h4 id=&quot;switch-the-code-push-deployment-key&quot;&gt;Switch the Code Push Deployment Key&lt;/h4&gt;
&lt;p&gt;This code snippet from &lt;strong&gt;/config/dev.xml&lt;/strong&gt; shows where we declare the Code Push deployment key.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;platform&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;preference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CodePushDeploymentKey&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YOUR-ANDROID-DEVELOPMENT-DEPLOYMENT-KEY&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;platform&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ios&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;preference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CodePushDeploymentKey&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YOUR-IOS-DEVELOPMENT-DEPLOYMENT-KEY&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We don’t want to use the same key in both development and production, so we’ll use a different key in &lt;strong&gt;/config/release.xml&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;platform&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;preference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CodePushDeploymentKey&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YOUR-ANDROID-PRODUCTION-DEPLOYMENT-KEY&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;platform&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ios&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;preference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CodePushDeploymentKey&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YOUR-IOS-PRODUCTION-DEPLOYMENT-KEY&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;give-each-branch-a-custom-gulp-task&quot;&gt;Give each branch a custom Gulp task&lt;/h3&gt;
&lt;p&gt;Our release-specific config files are defined in code, but we still need to move them to a place where Cordova can find them at build time during CI. To move the config files dynamically, we’ll create a gulp task that VSTS can run immediately before Cordova build. Each gulp task will rename the config files and copy them to the appropriate directory. So, for example, &lt;code class=&quot;highlighter-rouge&quot;&gt;gulp release&lt;/code&gt; will:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Rename release.xml to config.xml and move it to the project root&lt;/li&gt;
  &lt;li&gt;Rename release.js to config.js and move it to /www/js/&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The ✨magic✨ happens in &lt;strong&gt;gulpfile.js:&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// prepare for public release&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;release&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./config/release.xml&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;config.xml&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./config/release.js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;config.js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./www/js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// prepare for beta distribution&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;beta&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./config/beta.xml&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;config.xml&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./config/beta.js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;config.js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./www/js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// prepare for development environment&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;dev&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./config/dev.xml&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;config.xml&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./config/dev.js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;config.js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gulp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;./www/js&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can use the same technique to customize anything in response to a build definition. And as you might have guessed, the technique isn’t limited to XML and JS. You can do all sorts of things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Change your app icon and splash screen to display a “beta” badge only in the beta release&lt;/li&gt;
  &lt;li&gt;Use a different API key for services like maps, analytics, data sync, etc. in production&lt;/li&gt;
  &lt;li&gt;Show features only in development builds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That wasn’t too bad, now was it? Take a break, catch your breath and regroup by playing a song in your headphones. I recommend &lt;a href=&quot;https://sleepyeyesofdeath.bandcamp.com/album/street-lights-for-a-ribcage&quot;&gt;“Street Lights for a Ribcage” by Sleepy Eyes of Death.&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;configure-vsts&quot;&gt;Configure VSTS&lt;/h2&gt;
&lt;p&gt;Our code is setup to dynamically rename and move config.xml using a Gulp task. Now, we need to automate the process using continuous integration in VSTS. For each build definition, we’ll add a Gulp build step to move and rename the config files. Then, we’ll change the trigger branch and source repository to match the branch.&lt;/p&gt;

&lt;p&gt;Using the same instance of VSTS that you configured in Parts &lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;I&lt;/a&gt;, &lt;a href=&quot;/2016/04/26/beta-testing-with-hockeyapp.html&quot;&gt;II&lt;/a&gt; and &lt;a href=&quot;/2016/05/01/publish-without-resubmitting-to-the-app-store.html&quot;&gt;III&lt;/a&gt; of this series, login and navigate to the “BUILD” tab where you will edit the “Android-Dev” build definition.&lt;/p&gt;

&lt;p&gt;Add a space-delimited array of Gulp tasks for &lt;code class=&quot;highlighter-rouge&quot;&gt;dev sass test&lt;/code&gt;. In order, these will:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;dev&lt;/code&gt; rename and moves /config/dev.xml to /config.xml&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;sass&lt;/code&gt; compile SASS to CSS&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;test&lt;/code&gt; execute our Karma+Jasmine tests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-07-build-step-gulp.png&quot; alt=&quot;Configure your Gulp Task build step&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Under the “Triggers” tab, change continuous integration to trigger when code changes in the &lt;strong&gt;dev&lt;/strong&gt; branch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-07-build-trigger.png&quot; alt=&quot;Configure your trigger&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Under the “Prepository” tab, change the default branch to use &lt;strong&gt;dev&lt;/strong&gt; branch as the source repository.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-07-build-repository.png&quot; alt=&quot;Configure your source repository&quot; /&gt;&lt;/p&gt;

&lt;p&gt;“Android-Beta” and “Android-Release” will follow a similar pattern. You can see a full list of the values below.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Build Definition&lt;/th&gt;
      &lt;th&gt;Gulp Tasks&lt;/th&gt;
      &lt;th&gt;Source Repository&lt;/th&gt;
      &lt;th&gt;Trigger Filter&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Android-Dev&lt;/td&gt;
      &lt;td&gt;dev sass test&lt;/td&gt;
      &lt;td&gt;Dev&lt;/td&gt;
      &lt;td&gt;Dev&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Android-Beta&lt;/td&gt;
      &lt;td&gt;beta sass&lt;/td&gt;
      &lt;td&gt;Beta&lt;/td&gt;
      &lt;td&gt;Beta&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Android-Release&lt;/td&gt;
      &lt;td&gt;release sass&lt;/td&gt;
      &lt;td&gt;Release&lt;/td&gt;
      &lt;td&gt;Release&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;checking-our-work&quot;&gt;Checking Our Work&lt;/h3&gt;
&lt;p&gt;You’ve done all the hard work, now pluck the fruit of your labors. 🍊 In Terminal:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# trigger CI for &quot;Android-Dev&quot; by pushing a change&lt;/span&gt;
git checkout dev
git touch readme.md
git commit -m &lt;span class=&quot;s2&quot;&gt;&quot;triggering dev CI&quot;&lt;/span&gt;
git push

&lt;span class=&quot;c1&quot;&gt;# trigger CI for &quot;Android-Beta&quot; by pushing a change&lt;/span&gt;
git checkout beta
git merge dev
git push

&lt;span class=&quot;c1&quot;&gt;# trigger CI for &quot;Android-Release&quot; by pushing a change&lt;/span&gt;
git checkout release
git merge beta
git push&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When you return to the VSTS portal, you’ll find the CI builds waiting in the queue. Double-click on any of the builds to see the build output in real-time. Celebrate with a song in your headphones. I recommend &lt;a href=&quot;https://www.youtube.com/watch?v=J8AisTXgAGA&quot;&gt;“The Commander Thinks Aloud” by The Long Winters.&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;mobile-devops-a-four-part-series&quot;&gt;Mobile DevOps: A Four Part Series&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;Part One: Continuous Integration for Cordova Apps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/04/26/beta-testing-with-hockeyapp.html&quot;&gt;Part Two: Beta Testing with HockeyApp&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/05/01/publish-without-resubmitting-to-the-app-store.html&quot;&gt;Part Three: Publish without Resubmitting to the App Store&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/05/07/maintaining-different-release-configurations.html&quot;&gt;Part Four: Maintaining Different Release Configurations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sat, 07 May 2016 00:00:00 +0000</pubDate>
        <link>http://ryanjsalva.com/2016/05/07/maintaining-different-release-configurations.html</link>
        <guid isPermaLink="true">http://ryanjsalva.com/2016/05/07/maintaining-different-release-configurations.html</guid>
        
        <category>cordova</category>
        
        <category>ionic</category>
        
        <category>devops</category>
        
        <category>vsts</category>
        
        <category>git</category>
        
        
      </item>
    
      <item>
        <title>Release Without Re-submitting to the App Store</title>
        <description>&lt;p&gt;&lt;strong&gt;This is part three of a four part series and assumes you’ve already set-up continuous integration with our &lt;a href=&quot;http://github.com/ryanjsalva/superfly&quot;&gt;sample project&lt;/a&gt;. If you haven’t already, I highly recommend reading part one, &lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;Continuous Integration for Cordova Apps&lt;/a&gt;, where we setup the build server used in this tutorial.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;javascripts-unfair-advantage&quot;&gt;JavaScript’s Unfair Advantage&lt;/h2&gt;
&lt;p&gt;Mobile apps created with JavaScript (e.g. &lt;a href=&quot;http://cordova.apache.org&quot;&gt;Apache Cordova&lt;/a&gt;, &lt;a href=&quot;https://facebook.github.io/react-native/&quot;&gt;ReactNative&lt;/a&gt;) enjoy a unique privilege within app stores. Unlike compiled apps that developers can only update through the app store, JavaScript apps can download and install updates from any trusted server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagine this:&lt;/strong&gt; you’ve just released a major update when… BAM! The one-star reviews start pouring in due to a crashing bug. If you had to re-submit to the app store, you could be waiting days before the store approved and published your new package. By that time, many users will have abandoned your app. The nasty ones might have even written a few flaming store reviews. 🔥&lt;/p&gt;

&lt;p&gt;With over-the-air updates, you can deliver bug fixes directly to your customers by downloading new assets (e.g. HTML, CSS, JS, images) from the cloud and replacing the files packaged with your app. In other words, you can deliver updates immediately and on your own schedule.&lt;/p&gt;

&lt;h2 id=&quot;how-does-it-work&quot;&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;To understand how updates work, it’s helpful to get a high-level overview of the workflow:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;The first time you publish, you submit the &lt;strong&gt;full app&lt;/strong&gt; including both compiled code and web assets to the app store.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When you want to publish an update, you zip up your web assets (no compiled code) and publish them to the Code-Push cloud service. We’ll talk about different service providers in a moment.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;On some event (e.g. deviceready, resume, button click), each mobile device checks the Code-Push cloud service for an update.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If an update is available, each mobile device downloads the update and replaces the contents of &lt;strong&gt;/www&lt;/strong&gt; with your new code.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;div style=&quot;text-align:center;&quot;&gt;&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/nsOR4w2Bpuw?color=white&amp;amp;theme=light&amp;amp;loop=1&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;Of course, there are lots of optional detours along this path. For example, you could (1) give users the option to skip an update, (2) update a subset of files in &lt;strong&gt;/www,&lt;/strong&gt; or (3) release to only a subset of users. The service itself is very configurable.&lt;/p&gt;

&lt;h3 id=&quot;is-apple-really-cool-with-it&quot;&gt;Is Apple really cool with it?&lt;/h3&gt;
&lt;p&gt;Anytime I speak about bypassing the app store, someone invariably asks if this whole thing is really kosher with the app stores. The answer is, “Yes! Yes! A thousand times, yes!” In fact, it’s written into Apple’s developer agreement (emphasis mine). The same goes for Android.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-01-agreement.jpg&quot; alt=&quot;Sample Apple Developer Agreement&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;service-providers&quot;&gt;Service Providers&lt;/h3&gt;
&lt;p&gt;There are a number of plugins &amp;amp; services that make over-the-air updates possible. Cordova developers can choose between &lt;a href=&quot;http://docs.build.phonegap.com/en_US/tools_hydration.md.html&quot;&gt;PhoneGap Hydration&lt;/a&gt;, &lt;a href=&quot;http://ionic.io/#major-feature-deploy&quot;&gt;Ionic Deploy&lt;/a&gt; and &lt;a href=&quot;http://codepush.tools&quot;&gt;Code Push&lt;/a&gt;. Code Push also happens to be the defacto choice for most ReactNative developers, so our example will use it.&lt;/p&gt;

&lt;h2 id=&quot;lets-code-push&quot;&gt;Let’s code (push)&lt;/h2&gt;
&lt;p&gt;Again, our tutorial will use the &lt;a href=&quot;http://github.com/ryanjsalva/superfly&quot;&gt;superfly sample project&lt;/a&gt; and Visual Studio Team Services (VSTS) setup in &lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;part one of this series.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like other device capabilities, Code Push has it’s own Cordova plugin &lt;a href=&quot;https://www.npmjs.com/package/cordova-plugin-code-push&quot;&gt;(cordova-plugin-code-push)&lt;/a&gt; which has already been added to our sample project. You can see the reference in config.xml:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;plugin&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cordova-plugin-code-push&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;spec=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;~1.6.0-beta&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;create-your-code-push-service&quot;&gt;Create Your Code-Push Service&lt;/h3&gt;
&lt;p&gt;To register for the Code Push service, you must first install the Code Push CLI. From your terminal…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# install code-push globally&lt;/span&gt;
npm install -g code-push-cli

&lt;span class=&quot;c1&quot;&gt;# sign up for the free code-push service&lt;/span&gt;
code-push register&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This second command will open a browser where you should simply follow the registration process. As usual, it’s free to use, so don’t worry about pulling out the credit card. 💸&lt;/p&gt;

&lt;h3 id=&quot;manage-deployments&quot;&gt;Manage Deployments&lt;/h3&gt;
&lt;p&gt;Once you’ve registered, go back to the command line where we’ll register our first app with the Code Push service.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;code-push app add superfly&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Code Push will return two deployment keys: Staging and Production. These keys are used to identify your app when making a request to the Code Push service and will be saved in &lt;strong&gt;config.xml.&lt;/strong&gt; If you lose the key, don’t worry. You can retrieve it at any time by executing:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;code-push deployment list superfly -k&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Under most circumstances, you’ll want to deploy to iOS and Android separately. After all, if a bug fix is unique to Android, we don’t want to inconvenience iOS users with an unnecessary update. So, let’s prepare for the future by creating separate deployment keys for iOS and Android.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# rename the default Staging key for Android&lt;/span&gt;
code-push deployment rename superfly Staging Staging-Android

&lt;span class=&quot;c1&quot;&gt;# rename the default Production key for Android&lt;/span&gt;
code-push deployment rename superfly Production Production-Android

&lt;span class=&quot;c1&quot;&gt;# create a new Staging key for iOS&lt;/span&gt;
code-push deployment add superfly Staging-iOS

&lt;span class=&quot;c1&quot;&gt;# create a new Production key for iOS&lt;/span&gt;
code-push deployment add superfly Production-iOS

&lt;span class=&quot;c1&quot;&gt;# make sure we got it right&lt;/span&gt;
code-push deployment list superfly -k&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;add-the-deployment-key-to-your-app&quot;&gt;Add the Deployment Key to Your App&lt;/h3&gt;
&lt;p&gt;You’ve created deployment keys for each platform. Now, it’s time to save those deployment keys with your app. Open &lt;strong&gt;config.xml&lt;/strong&gt; in the superfly project and insert the Staging-Android and Staging-iOS deployment keys where indicated:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;platform&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;preference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CodePushDeploymentKey&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YOUR-ANDROID-DEPLOYMENT-KEY&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;platform&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ios&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;preference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CodePushDeploymentKey&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YOUR-IOS-DEPLOYMENT-KEY&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p class=&quot;pull-quote&quot;&gt;Notice that we’re hard-coding the deployment keys in config.xml. In part four of this series, I’ll show you how to dynamically change keys based on your deployment target (e.g. Staging or Production).&lt;/p&gt;

&lt;h3 id=&quot;safety-first&quot;&gt;Safety first!&lt;/h3&gt;
&lt;p&gt;Any Cordova app using version 5 or greater is required to use the &lt;a href=&quot;https://github.com/apache/cordova-plugin-whitelist&quot;&gt;whitelist plugin&lt;/a&gt; and declare a &lt;a href=&quot;http://taco.visualstudio.com/en-us/docs/cordova-security-whitlists/&quot;&gt;Content Security Policy (CSP)&lt;/a&gt;. The CSP essentially answers the question, “what domains are trusted to provide data?” To establish trust with the Code Push server, we need to add the following meta tag to &lt;strong&gt;index.html:&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;http-equiv=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Content-Security-Policy&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;script-src https://codepush.azurewebsites.net http://localhost:* &#39;self&#39; &#39;unsafe-inline&#39; &#39;unsafe-eval&#39;; media-src *&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Translated into English this CSP says, “It’s okay to execute JavaScript from…”&lt;/p&gt;

&lt;p&gt;👍 &lt;a href=&quot;https://codepush.azurewebsites.net&quot;&gt;https://codepush.azurewebsites.net&lt;/a&gt;&lt;br /&gt;
 👍 &lt;a href=&quot;http://localhost&quot;&gt;http://localhost&lt;/a&gt;&lt;br /&gt;
 👍 Any file referenced in root directory&lt;br /&gt;
 👍 Inline &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags&lt;br /&gt;
 👍 &lt;code class=&quot;highlighter-rouge&quot;&gt;eval()&lt;/code&gt; statements are also OK&lt;br /&gt;
 👍 Images, videos and other media can come from anywhere&lt;/p&gt;

&lt;h3 id=&quot;invoke-the-code-push-api&quot;&gt;Invoke the Code-Push API&lt;/h3&gt;
&lt;p&gt;We’ve already established our app’s identity by created deployment keys. Now, we need to tell the app how and when to ping the Code Push service for an update. For demonstration purposes we’re simply going to check for updates when clicking a menu item, but you may want to use app lifecycle events like &lt;code class=&quot;highlighter-rouge&quot;&gt;deviceready.&lt;/code&gt; Our button can be found in &lt;strong&gt;/www/templates/menu.html&lt;/strong&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Code Push --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;item item-icon-left&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ng-click=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;codePush()&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;i&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;icon ion-ios-cloud-download-outline&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;Check Update
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;… and the handler in &lt;strong&gt;/www/js/controllers.js&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;cm&quot;&gt;/* ---------------------------------------------------------------
   codepush check for update */&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;codePush&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;check for code push update&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updateDialogOptions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;updateTitle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Update&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;mandatoryUpdateMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;You will be updated to the latest version of the app.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;mandatoryContinueButtonLabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Continue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;optionalUpdateMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Update available. Install?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;optionalIgnoreButtonLabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;No&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;optionalInstallButtonLabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Yes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;syncOptions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;installMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;InstallMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ON_NEXT_RESTART&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;updateDialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updateDialogOptions&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;syncStatusCallback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;syncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;syncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Result (final) statuses&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UPDATE_INSTALLED&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;$ionicPopup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Sweet Success&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Restart your app to complete the update.&quot;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UP_TO_DATE&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;$ionicPopup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;All Good&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Your application is up to date.&quot;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UPDATE_IGNORED&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The user decided not to install the optional update.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ERROR&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;$ionicPopup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;@#$!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Something went wrong. Try restarting your app.&quot;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// Intermediate (non final) statuses&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CHECKING_FOR_UPDATE&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Checking for update.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;AWAITING_USER_ACTION&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Alerting user.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DOWNLOADING_PACKAGE&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Downloading package.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;INSTALLING_UPDATE&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Installing update&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;codePush&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;syncStatusCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;syncOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// --------------------------------------------------------------- */&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;While this looks complicated, it’s really quite simple once you break it down.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;updateDialogOptions {...}&lt;/code&gt; provides optional text and styling information&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;syncOptions {...}&lt;/code&gt; tells Code Push to replace the content of &lt;strong&gt;/www&lt;/strong&gt; on restart&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;syncStatusCallback&lt;/code&gt; handles the range of possible responses from the Code-Push service&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;window.codePush.sync(...)&lt;/code&gt; invokes the actual codePush API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The truth is that you could omit items 1-3 and just let Code Push do everything with &lt;code class=&quot;highlighter-rouge&quot;&gt;window.codePush.sync()&lt;/code&gt;, but it’s nice to see what’s possible with a fully customized implementation.&lt;/p&gt;

&lt;h2 id=&quot;automate-deployments-with-vsts&quot;&gt;Automate Deployments with VSTS&lt;/h2&gt;
&lt;p&gt;Similar to the steps followed in &lt;a href=&quot;/2016/04/26/beta-testing-with-hockeyapp.html&quot;&gt;part two of this series&lt;/a&gt;, we’re going to clone our pre-existing build definition and add a Code Push build step. Login to VSTS and navigate to the &lt;strong&gt;BUILD&lt;/strong&gt; tab. Right-click on the “Android-Build” definition and select &lt;strong&gt;Clone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-01-clone-build-definition.png&quot; alt=&quot;Clone build definition&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With the new build definition selected, click “Add build step” and choose “Code Push - Release” from the Deploy category.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-01-add-build-step.png&quot; alt=&quot;Add build step: Code Push - Release&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;configure-your-code-push-build-step&quot;&gt;Configure Your Code Push Build Step&lt;/h3&gt;
&lt;p&gt;Once you’ve added the build step, you’ll need to configure it as pictured below:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-01-configure-build-step.png&quot; alt=&quot;Configure Code Push build step&quot; /&gt;&lt;/p&gt;

&lt;table class=&quot;small&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Label&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Why?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Authentication Method&lt;/td&gt;
      &lt;td&gt;Access Key&lt;/td&gt;
      &lt;td&gt;Establishes trust with the Code Push cloud service&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Access Key&lt;/td&gt;
      &lt;td&gt;$(code-push-access-key)&lt;/td&gt;
      &lt;td&gt;To protect our access key, we’ll encrypt it in a variable&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;App Name&lt;/td&gt;
      &lt;td&gt;superfly&lt;/td&gt;
      &lt;td&gt;This is human readable app name published to the store&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Deployment&lt;/td&gt;
      &lt;td&gt;Staging&lt;/td&gt;
      &lt;td&gt;We’ll use staging while testing&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Update Contents Path&lt;/td&gt;
      &lt;td&gt;platforms/android/assets/www&lt;/td&gt;
      &lt;td&gt;Default location where Cordova outputs the web assets for Android&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Target Binary Version&lt;/td&gt;
      &lt;td&gt;^0.0.2&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;http://semver.org/&quot;&gt;Semantic Versioning.&lt;/a&gt; This says, “install the update if you’re installed app is of version 0.0.2+n”&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Rollout&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
      &lt;td&gt;Deploy this update to 100% of possible users&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Descrition&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;You can display this message to users before an update&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mandatory&lt;/td&gt;
      &lt;td&gt;Unchecked&lt;/td&gt;
      &lt;td&gt;Don’t require this update&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Disabled&lt;/td&gt;
      &lt;td&gt;Unchecked&lt;/td&gt;
      &lt;td&gt;We want to run this build step&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Enabled&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;We want to run this build step&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Continue on error&lt;/td&gt;
      &lt;td&gt;Unchecked&lt;/td&gt;
      &lt;td&gt;Stop the presses when there’s a build error&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Always run&lt;/td&gt;
      &lt;td&gt;Unchecked&lt;/td&gt;
      &lt;td&gt;Since we never continue on error, it’s okay to leave this unchecked&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;establish-trust-between-vsts-and-code-push&quot;&gt;Establish Trust Between VSTS and Code Push&lt;/h3&gt;
&lt;p&gt;As you probably noticed above, we referenced a VSTS build variable to store &lt;code class=&quot;highlighter-rouge&quot;&gt;$(code-push-access-key)&lt;/code&gt;, but haven’t actually created one yet. Remember, the Access Key establishes a bond of trust between VSTS and the Code Push service so that VSTS can deploy on your behalf. To get your Access Key, go to the command line and execute:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# create the access key&lt;/span&gt;
code-push access-key add superfly&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This command will return a 32 character key. Back in VSTS, go to the &lt;strong&gt;Variables&lt;/strong&gt; tab and add a variable named “code-push-access-key” using your new key. To protect it from prying eyes, select the lock icon to the right of the input box.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-01-add-variable.png&quot; alt=&quot;Add the code-push-access-key variable&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;deploy-on-commit&quot;&gt;Deploy on Commit&lt;/h3&gt;
&lt;p&gt;At last, we’re ready to commit our code changes and use Code Push to deploy our update over-the-air. Make sure  “Continuous Integration” is checked under the Triggers tab and save this build definition as “Android-Release.”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-01-triggers.png&quot; alt=&quot;Triggers set to use Continuous Integration&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With “Continuous Integration” checked, this build definition will run every time someone commits code to the master branch. To trigger the next build, open your terminal and push the latest changes…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;git commit -m &lt;span class=&quot;s2&quot;&gt;&quot;Add code push&quot;&lt;/span&gt;
git push&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Once the files have transferred, you should be able to see your build complete in VSTS. Select “Android-Release” then double-click on the build number under “Queued Builds”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-05-01-queued-build.png&quot; alt=&quot;Android-Release build executing&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;open-the-app-and-check-for-updates&quot;&gt;Open the App and Check for Updates&lt;/h2&gt;
&lt;p&gt;To see Code Push in action, deploy the app to a local device or emulator. I’m using a Nexus 5 running Android Marshmallow (because I’m fresh like that). From your terminal…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/Code/superfly
cordova run android --device&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;From the running app, open the slide-in menu and select “Code Push Update”&lt;/p&gt;

&lt;p class=&quot;max-width-50 center shadow&quot;&gt;&lt;img src=&quot;/assets/2016-05-01-app.png&quot; alt=&quot;App running on Android&quot; /&gt;&lt;/p&gt;

&lt;p&gt;TA-DA! Your app will update on restart.&lt;/p&gt;

&lt;h2 id=&quot;mobile-devops-a-four-part-series&quot;&gt;Mobile DevOps: A Four Part Series&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;Part One: Continuous Integration for Cordova Apps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/04/26/beta-testing-with-hockeyapp.html&quot;&gt;Part Two: Beta Testing with HockeyApp&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/05/01/publish-without-resubmitting-to-the-app-store.html&quot;&gt;Part Three: Publish without Resubmitting to the App Store&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/05/07/maintaining-different-release-configurations.html&quot;&gt;Part Four: Maintaining Different Release Configurations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sun, 01 May 2016 00:00:00 +0000</pubDate>
        <link>http://ryanjsalva.com/2016/05/01/publish-without-resubmitting-to-the-app-store.html</link>
        <guid isPermaLink="true">http://ryanjsalva.com/2016/05/01/publish-without-resubmitting-to-the-app-store.html</guid>
        
        <category>cordova</category>
        
        <category>ionic</category>
        
        <category>devops</category>
        
        <category>code-push</category>
        
        
      </item>
    
      <item>
        <title>Beta Test with HockeyApp</title>
        <description>&lt;p&gt;&lt;strong&gt;This is part two of a four part series and assumes you’ve already set-up continuous integration with our &lt;a href=&quot;http://github.com/ryanjsalva/superfly&quot;&gt;sample project&lt;/a&gt;. If you haven’t already, I highly recommend reading &lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;Continuous Integration for Cordova Apps.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unit tests and end-to-end tests provide a great sanity check before pushing code into source control, but the best bugs are often discovered through manual testing. Real human users just have a better sense for when something isn’t “right.”&lt;/p&gt;

&lt;p&gt;For beta testing, I prefer &lt;a href=&quot;https://hockeyapp.net&quot;&gt;HockeyApp&lt;/a&gt; over &lt;a href=&quot;https://developer.apple.com/testflight/&quot;&gt;Apple TestFlight&lt;/a&gt; or the &lt;a href=&quot;http://developer.android.com/distribute/engage/beta.html&quot;&gt;Google beta program&lt;/a&gt; because it allows me to distribute to &lt;strong&gt;unlimited beta testers, collect crash analytics and user feedback.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With HockeyApp, I can:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Upload my apps manually through the website or through a CI system&lt;/li&gt;
  &lt;li&gt;Distribute to a controlled group of iOS, Android and Windows users&lt;/li&gt;
  &lt;li&gt;Collect detailed crash reporting and analytics&lt;/li&gt;
  &lt;li&gt;Collect user feedback (e.g. send messages, attach screenshots, threaded communication)&lt;/li&gt;
  &lt;li&gt;Control the app version installed on user devices&lt;/li&gt;
  &lt;li&gt;Integrate with most popular bug tracking systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HockeyApp is also free for accounts with up to 2 apps, so it’s a no-brainer to try.&lt;/p&gt;

&lt;h3 id=&quot;get-started&quot;&gt;Get Started&lt;/h3&gt;

&lt;p&gt;While HockeyApp allows you to manually upload app packages through their website, we want to automate releases through our build server on VSTS. So, let’s start by creating a free account on &lt;a href=&quot;https://hockeyapp.net&quot;&gt;http://www.hockeyapp.net&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-26-signup.png&quot; alt=&quot;Signup for a HockeyApp account&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once you’ve created an account, we need to establish trust with VSTS by sharing an API Token. Go to &lt;strong&gt;Account Settings&lt;/strong&gt; within the HockeyApp portal and select &lt;strong&gt;API Tokens&lt;/strong&gt; in the sidebar. From here, create an “All Apps” token with “Full Access” rights. HockeyApp will generate a 32-character token. You’re going to use that token in a minute, but just keep the tab open for now.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-26-create-token.png&quot; alt=&quot;Create an API token&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Switch to a new browser tab and open the &lt;strong&gt;Build&lt;/strong&gt; tab of your VSTS account. Right-click on the &lt;strong&gt;Android-Build&lt;/strong&gt; definition that you previously created and select &lt;strong&gt;Clone.&lt;/strong&gt; After all, there’s no need to start from scratch again. In the cloned build definition, add the &lt;strong&gt;HockeyApp&lt;/strong&gt; build step after “Cordova Build”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-26-add-build-step.png&quot; alt=&quot;Add Hockey App Build Step&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To get the &lt;strong&gt;HockeyApp Connection&lt;/strong&gt;, you’ll need to take one extra step. Click &lt;strong&gt;Manage&lt;/strong&gt; adjacent to the input box. In the new window, select &lt;strong&gt;HockeyApp&lt;/strong&gt;, then enter the 32-character token you created in HockeyApp.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-26-service-endpoint.png&quot; alt=&quot;Add Service Endpoint for HockeyApp&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When you return to VSTS, simply click the refresh button adjacent to the drop-down to make your new connection appear. Configure your build step as pictured below:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-26-hockeyapp-build-step.png&quot; alt=&quot;Configure your HockeyApp build step&quot; /&gt;&lt;/p&gt;

&lt;table class=&quot;small&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Label&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Why?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;HockeyApp Connection&lt;/td&gt;
      &lt;td&gt;All Apps&lt;/td&gt;
      &lt;td&gt;Points to the API token you created in HockeyApp and establishes trust&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;App ID&lt;/td&gt;
      &lt;td&gt;com.ryanjsalva.superfly&lt;/td&gt;
      &lt;td&gt;This is the default App ID of my sample project. You can find/change it by looking for the &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;widget&amp;gt;&lt;/code&gt; tag in config.xml&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Binary File Path&lt;/td&gt;
      &lt;td&gt;platforms/android/build/outputs/apk/android-debug.apk&lt;/td&gt;
      &lt;td&gt;Default location where Cordova outputs the native app package&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Symbols File Path&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Not used by our project&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Native Library File Path&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Not used by our project&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release Notes (File)&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Not used by our project&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release Notes&lt;/td&gt;
      &lt;td&gt;Recommended update.&lt;/td&gt;
      &lt;td&gt;Because if we say it’s “recommended”, surely they’ll install it, right?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Publish&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;Immediately publishes to HockeyApp&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mandatory&lt;/td&gt;
      &lt;td&gt;Unchecked&lt;/td&gt;
      &lt;td&gt;Not a required update&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Notify Users?&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;Users are more likely to install if you tell them something is available&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tags&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;No tags mean the update goes to everyone&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Teams&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;No teams mean the update goes to everyone&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Users&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;No users mean the udpate goes to everyone&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Enabled&lt;/td&gt;
      &lt;td&gt;Checked&lt;/td&gt;
      &lt;td&gt;We want to run this build step&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Continue on error&lt;/td&gt;
      &lt;td&gt;Unchecked&lt;/td&gt;
      &lt;td&gt;Stop the presses when there’s a build error&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Always run&lt;/td&gt;
      &lt;td&gt;Unchecked&lt;/td&gt;
      &lt;td&gt;Since we never continue on error, it’s okay to leave this unchecked&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Save this build definition as &lt;strong&gt;Android-Beta&lt;/strong&gt;. We won’t build yet because there are still a few code changes necessesary, but celebrate anyway with a song in your headphones. I recommend &lt;a href=&quot;https://www.youtube.com/watch?v=veNzHk-ZNEs&quot;&gt;The Modern Lovers’ Girlfriend&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;invoke-hockeyapp-from-code&quot;&gt;Invoke HockeyApp from Code&lt;/h3&gt;

&lt;p&gt;The HockeyApp SDK is delivered as a &lt;a href=&quot;https://github.com/bitstadium/HockeySDK-Cordova&quot;&gt;Cordova plugin&lt;/a&gt;. That plugin is already installed in our &lt;a href=&quot;http://github.com/ryanjsalva/superfly&quot;&gt;sample project&lt;/a&gt; (you’re welcome), but we need to invoke it. Fortunately, that only requires modifying a few lines. In &lt;strong&gt;/www/js/app.js&lt;/strong&gt;, uncomment the following line and replace “APP_ID” with the value for &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;widget id=&quot;APP_ID&quot;&amp;gt;&lt;/code&gt; found in &lt;strong&gt;config.xml.&lt;/strong&gt; Assuming you don’t create your own APP_ID, the value will be “com.ryanjsalva.superfly”.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;hockeyapp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;APP_ID&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
 &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that the APP_ID needs to match in three places:&lt;/p&gt;

&lt;table class=&quot;small&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Location&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Why?&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;/config.xml&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;widget id=&quot;APP_ID&quot;&amp;gt;&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Authoritative source for APP_ID&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;/www/js/app.js&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hockeyapp.start(null,null,&quot;APP_ID&quot;)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Establishes app identity when calling into the HockeyApp API&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;VSTS&lt;/td&gt;
      &lt;td&gt;HockeyApp build step configuration&lt;/td&gt;
      &lt;td&gt;I honestly don’t know&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Next, we need to uncomment a few lines from &lt;strong&gt;/www/js/controllers.js&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fakeCrash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;fake crash&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;hockeyapp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addMetaData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;someCustomProp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;anotherProp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Custom Value&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;hockeyapp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forceCrash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// hockeyapp send feedback&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sendFeedback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;send feedback&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;hockeyapp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;feedback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// hockeyapp check for update&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;checkForUpdate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;check for HockeyApp update&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;hockeyapp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;trackEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Check for Update&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;hockeyapp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;checkForUpdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;These methods will be invoked when we click the corresponding menu items in our app’s menu bar. As you can see, there’s not much to them. The one thing I’d call out is &lt;code class=&quot;highlighter-rouge&quot;&gt;hockeyapp.addMetaData([json])&lt;/code&gt;. You can invoke this method at any time to record &lt;em&gt;n&lt;/em&gt; parameters describing your runtime state. This can be tremendously helpful if you need help debugging you app in the wild.&lt;/p&gt;

&lt;h3 id=&quot;run-that-build-you-crazy-kid&quot;&gt;Run that build, you crazy kid&lt;/h3&gt;

&lt;p&gt;Now, you’re ready to automate your next build with another commit. Go back to Terminal and run:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;git commit -m &lt;span class=&quot;s2&quot;&gt;&quot;enabled hockeyapp&quot;&lt;/span&gt;
git push&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When you return to VSTS, you should find a build in the queue for &lt;strong&gt;Android-Beta.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;install-hockeyapp-on-your-android-device&quot;&gt;Install HockeyApp on your Android Device&lt;/h2&gt;

&lt;p&gt;While that’s building, grab your Android phone and install &lt;a href=&quot;https://www.hockeyapp.net/apps/&quot;&gt;HockeyApp&lt;/a&gt;. HockeyApp is also available for iOS and Windows, but this tutorial only builds for Android… so, hopefully, you’ve got an Android phone handy 😛&lt;/p&gt;

&lt;p&gt;After authenticating on your Android device – and assuming the beta build has completed – you should see an app appear in your HockeyApp mobile client. Install and open it. You’ll find at least two fun features to try in the sample app sidebar menu:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Fake Crash&lt;/strong&gt; will force the mobile app to crash so you have a chance to explore HockeyApp’s crash analytics&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Send Feedback&lt;/strong&gt; allows beta testers to submit feedback to the development team directly through the app. Don’t forget to try attaching a picture (e.g. a screenshot) where you can annotate with crude finger drawings.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you’ve had a chance to play with the app and forced a few crashes, go back to the HockeyApp portal. With a few minutes of exploring, you’ll find lots of neat features. Listen to a song in your headphones while exploring, I recommend &lt;a href=&quot;https://soundcloud.com/bit-shifter/march-of-the-nucleotides-mrna&quot;&gt;“March of the Nucleotides by Bit Shifter.”&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;mobile-devops-a-four-part-series&quot;&gt;Mobile DevOps: A Four Part Series&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;Part One: Continuous Integration for Cordova Apps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/04/26/beta-testing-with-hockeyapp.html&quot;&gt;Part Two: Beta Testing with HockeyApp&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/05/01/publish-without-resubmitting-to-the-app-store.html&quot;&gt;Part Three: Publish without Resubmitting to the App Store&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/05/07/maintaining-different-release-configurations.html&quot;&gt;Part Four: Maintaining Different Release Configurations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Tue, 26 Apr 2016 00:00:00 +0000</pubDate>
        <link>http://ryanjsalva.com/2016/04/26/beta-testing-with-hockeyapp.html</link>
        <guid isPermaLink="true">http://ryanjsalva.com/2016/04/26/beta-testing-with-hockeyapp.html</guid>
        
        <category>cordova</category>
        
        <category>ionic</category>
        
        <category>devops</category>
        
        <category>hockeyapp</category>
        
        
      </item>
    
      <item>
        <title>Continuous Integration for Cordova Apps</title>
        <description>&lt;p&gt;&lt;strong&gt;This is part one of a four part series focused on devops for mobile apps. It assumes you’ve already installed &lt;a href=&quot;https://nvm.sh&quot;&gt;NVM&lt;/a&gt;, &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node&lt;/a&gt;, &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;NPM&lt;/a&gt;, &lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt;, &lt;a href=&quot;https://gulpjs.com&quot;&gt;Gulp&lt;/a&gt;, &lt;a href=&quot;https://cordova.apache.org&quot;&gt;Cordova&lt;/a&gt; and &lt;a href=&quot;https://ionicframework.com&quot;&gt;Ionic&lt;/a&gt;. I also recommend using &lt;a href=&quot;http://code.visualstudio.com&quot;&gt;VS Code&lt;/a&gt; for your code editor, but use whatever’s comfortable. All steps work equally well on Mac and Windows.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;why-continuous-integration&quot;&gt;Why Continuous Integration?&lt;/h3&gt;

&lt;p&gt;Continuous Integration is just fancy talk for “my app builds whenever I commit my code to source control.” In theory, it enables developers to release after every commit, but that’s not why most developers use it. The truly useful thing is that you can “decorate” your build with other scripts. For example, you can auto-magically:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Run unit, functional or UI tests to measure code quality&lt;/li&gt;
  &lt;li&gt;Notify other team members that a build is available&lt;/li&gt;
  &lt;li&gt;Distribute your app to beta testers&lt;/li&gt;
  &lt;li&gt;Publish your app to the store&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Imagine this:&lt;/strong&gt; you start by working on a new feature in your personal branch. Excited to share your work, you merge with the dev branch where unit tests automatically run and pass.&lt;/p&gt;

&lt;p&gt;Satisfied that code quality is… well, pass-able, you merge to the beta branch where your new feature is automatically distributed to beta testers for manual testing. Beta testers using Android devices notice that the layout is broken on views using your new feature, so they send feedback from within the app along with a screenshot. You fix the bug, then merge with dev and beta where unit tests and beta distribution auto-magically happen again.&lt;/p&gt;

&lt;p&gt;Confident that your feature is ready for the public, you merge with the release branch which triggers another release. Because your app is built with Cordova, you can update without re-submitting to the app store. Your build server pushes a new release and users get a notification. “Hey, buddy. A new version is available. Update now.”&lt;/p&gt;

&lt;p class=&quot;pull-quote&quot;&gt;Running unit tests, distributing to beta testers, releasing bug fixes over-the-air… all these steps happen automatically when you commit to a branch under watch.&lt;/p&gt;

&lt;p&gt;Over the next four blog posts we’ll…&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Setup continuous integration&lt;/li&gt;
  &lt;li&gt;Distribute our app to beta testers for manual testing&lt;/li&gt;
  &lt;li&gt;By-pass the app store to release bug fixes&lt;/li&gt;
  &lt;li&gt;Customize our build steps to use a different config for each release type – i.e. dev, beta, release.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;get-the-sample-project&quot;&gt;Get the Sample Project&lt;/h3&gt;
&lt;p&gt;Start by cloning the sample project into your local directory and restoring all the npm packages&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;git clone http://github.com/ryanjsalva/superfly
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;superfly
npm install&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;create-your-project-in-vsts&quot;&gt;Create Your Project in VSTS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx&quot;&gt;Visual Studio Team Services (VSTS)&lt;/a&gt; provides three basic services:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Git-based source control&lt;/li&gt;
  &lt;li&gt;Cross-platform build automation&lt;/li&gt;
  &lt;li&gt;Agile team management (e.g. scrum boards, backlogs, bug tracking)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a lot of ways, you can think of VSTS as the union between GitHub, Atlassian and Jenkins (if you’re familiar with those). By combining all three services in the same tool, we can automate a lot of the grunt work. As of this writing, it’s free for teams of 6 or less. Once you’ve logged in, you’ll see this page:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-25-vsts-new-project.png&quot; alt=&quot;VSTS New Project&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;pull-quote&quot;&gt;Side note: It’s almost criminal that you can get a pre-configured automated build environment for free these days. What a time to be alive.&lt;/p&gt;

&lt;p&gt;Create a new project and name it &lt;code class=&quot;highlighter-rouge&quot;&gt;superfly&lt;/code&gt;. On the next screen, choose to start by “adding code.” VSTS will provide you with a git URI that you can run in the terminal. Now we just need to push our first commit to VSTS.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;git remote add origin https://ryanjsalva.visualstudio.com/DefaultCollection/_git/superfly
git commit -m &lt;span class=&quot;s2&quot;&gt;&quot;first commit&quot;&lt;/span&gt;
git push&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Congratulations! You’ve setup your dev environment and source control. Celebrate with an awesome song on your headphones. I recommend anything from &lt;a href=&quot;https://soundcloud.com/burtonsnowgod/girl-talk-feed-the-animals-1&quot;&gt;Girl Talk’s Feed the Animals&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;lets-get-unit-tests-running&quot;&gt;Let’s Get Unit Tests Running&lt;/h3&gt;
&lt;p&gt;Like most &lt;a href=&quot;https://angularjs.org&quot;&gt;Angular&lt;/a&gt; projects, our sample app uses &lt;a href=&quot;https://karma-runner.github.io/&quot;&gt;Karma&lt;/a&gt; and &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; to run unit tests found in the &lt;code class=&quot;highlighter-rouge&quot;&gt;/tests&lt;/code&gt; directory. Our sample project includes a Gulp task to invoke these tests.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;gulp.task&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;test&#39;&lt;/span&gt;, &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  new Server&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;
    configFile: __dirname + &lt;span class=&quot;s1&quot;&gt;&#39;/tests/karma.conf.js&#39;&lt;/span&gt;,
    singleRun: &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;.start&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;;
&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you’re not already familiar with &lt;a href=&quot;https://gulpjs.com&quot;&gt;Gulp&lt;/a&gt;, it’s simply a build task manager. Using Gulp, you can create build tasks that move, concatenate, obfuscate, prettify or other wise modify files. You can see it work by opening terminal and executing &lt;code class=&quot;highlighter-rouge&quot;&gt;gulp test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This task runs our unit tests in a headless browser called &lt;a href=&quot;https://phantomjs.org/&quot;&gt;PhantomJS&lt;/a&gt;. If everything worked correctly, you should see the following response:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;k&quot;&gt;Using &lt;/span&gt;gulpfile ~/Desktop/superfly/gulpfile.js
Starting &lt;span class=&quot;s1&quot;&gt;&#39;test&#39;&lt;/span&gt;...
22 04 2016 21:38:37.271:INFO karma: Karma v0.13.22 server started at http://localhost:9876/
22 04 2016 21:38:37.277:INFO launcher: Starting browser PhantomJS
22 04 2016 21:38:38.398:INFO PhantomJS 1.9.8 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Mac OS X 0.0.0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Connected on socket /#WzMqeZ4EfOqhVS27AAAA with id 41819505
PhantomJS 1.9.8 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Mac OS X 0.0.0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Executed 2 of 2 SUCCESS &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0.007 secs / 0.012 secs&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Finished &lt;span class=&quot;s1&quot;&gt;&#39;test&#39;&lt;/span&gt; after 1.4 s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;run-tests-on-every-push&quot;&gt;Run Tests on Every Push&lt;/h3&gt;
&lt;p&gt;Now that we have tests running locally through a manual step, we want to run them in the cloud automatically. Head back to VSTS and click on the “BUILD” tab at the top. From here, we’ll create our first build definition by clicking the [+] icon.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-25-build-definition-empty.png&quot; alt=&quot;VSTS Empty list of build defitions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Name your build definition “Android-Build”, choose the &lt;strong&gt;Empty&lt;/strong&gt; template and select the &lt;strong&gt;Continuous Integration&lt;/strong&gt; checkbox since we want builds to run on every push. Otherwise, stick with the defaults.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-25-build-definition-new.png&quot; alt=&quot;VSTS New Build Definition&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Each build definition is comprised of multiple build steps executed in serial order. Let’s add some. Click &lt;strong&gt;Add build step&lt;/strong&gt; and add the following steps in this order:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;npm:&lt;/strong&gt; to download all the node_modules not saved in our repository&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Gulp:&lt;/strong&gt; to run our “test” task&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cordova Build:&lt;/strong&gt; to build the app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the most part, you should be able to go with all the defaults. To configure each build step, select it and set the following values:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-25-build-step-npm.png&quot; alt=&quot;VSTS Build Step: npm&quot; /&gt;
&lt;img src=&quot;/assets/2016-04-25-build-step-gulp.png&quot; alt=&quot;VSTS Build Step: gulp&quot; /&gt;
&lt;img src=&quot;/assets/2016-04-25-build-step-cordova.png&quot; alt=&quot;VSTS Build Step: Cordova Build&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Under the Cordova Build step, notice the &lt;code class=&quot;highlighter-rouge&quot;&gt;$(configuration)&lt;/code&gt; variable. This allows us to dynamically change the build configuration (e.g. debug or release) at queue time, but it requires a little extra setup. Switch to the &lt;strong&gt;Variables&lt;/strong&gt; tab and add a new variable for “configuration” before clicking &lt;strong&gt;SAVE&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-25-build-definition-variables.png&quot; alt=&quot;VSTS Build Definition: Variables&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Since we’re here, we might as well take a look at some of the other interesting fields, though no changes will be required. Under the &lt;strong&gt;Triggers&lt;/strong&gt; tab, we see that new builds will be triggered every time there’s a push to the master branch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-25-build-definition-triggers.png&quot; alt=&quot;VSTS Build Definition: Triggers&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Under the &lt;strong&gt;Repository&lt;/strong&gt; tab, we see that new builds will also use source from the master branch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2016-04-25-build-definition-repository.png&quot; alt=&quot;VSTS Build Definition: Repository&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you haven’t already, click &lt;strong&gt;SAVE&lt;/strong&gt; so that we can trigger our first CI build. Go back to terminal, and commit a change:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;touch readme.md
git commit -m &lt;span class=&quot;s2&quot;&gt;&quot;created empty readme&quot;&lt;/span&gt;
git push&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can see your build spool by clicking &lt;strong&gt;Builds&lt;/strong&gt;, then navigating to the &lt;strong&gt;Queued&lt;/strong&gt; tab and double-clicking the queued build.&lt;/p&gt;

&lt;p&gt;When it’s all done, you should see a build progress report declaring success. Celebrate with another song in your headphones. I recommend &lt;a href=&quot;https://www.youtube.com/watch?v=WVEhNHIzJec&quot;&gt;“I Don’t Want to Get Over You” by the Magnetic Fields.&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;mobile-devops-a-four-part-series&quot;&gt;Mobile DevOps: A Four Part Series&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/04/25/continuous-integration-for-cordova-apps.html&quot;&gt;Part One: Continuous Integration for Cordova Apps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/04/26/beta-testing-with-hockeyapp.html&quot;&gt;Part Two: Beta Testing with HockeyApp&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/05/01/publish-without-resubmitting-to-the-app-store.html&quot;&gt;Part Three: Publish without Resubmitting to the App Store&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/05/07/maintaining-different-release-configurations.html&quot;&gt;Part Four: Maintaining Different Release Configurations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Mon, 25 Apr 2016 00:00:00 +0000</pubDate>
        <link>http://ryanjsalva.com/2016/04/25/continuous-integration-for-cordova-apps.html</link>
        <guid isPermaLink="true">http://ryanjsalva.com/2016/04/25/continuous-integration-for-cordova-apps.html</guid>
        
        <category>cordova</category>
        
        <category>ionic</category>
        
        <category>devops</category>
        
        <category>vsts</category>
        
        
      </item>
    
  </channel>
</rss>
