Monday, 31 October 2016

Maintain Separate Configs for Dev, Beta and Release Build Targets

This is part four of a four part series (😆 🎉 👏) and assumes you’ve already set-up continuous integration with our sample project. If you haven’t already, I highly recommend reading part one, Continuous Integration for Cordova Apps, where we setup the build server used in this tutorial.

Change Your App’s Configuration Based on Branch

In parts I - III, we used Visual Studio Team Services (VSTS) to setup build definitions for unit testingbeta testing and over-the-air updates. 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.
  1. Android-Dev: runs Karma+Jasmine unit tests to ensure code quality
  2. Android-Beta: distributes the app to manual testers via HockeyApp
  3. Android-Release: distributes app updates to the public via Code Push
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.
Cascading code flow and releases

Two Big Advantages

By creating a unique branch for each release type, we gain two big advantages.
First, we can maintain a different set of config files for each branch – 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.
Second, we can trigger different build steps for each branch. 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.
To realize our dream, we must:
  • Update the Source Code
    1. Create a branch for each build definition
    2. Create config files for each build definition
    3. Add gulp tasks to move and rename the config files before build
  • Configure VSTS
    1. Change each build definition to trigger based on branch
    2. Source the build from the appropriate repository
    3. Add a custom gulp build step within each build definition

Update the Source Code

Let’s start by creating three new branches at the terminal:
# create a dev branch to trigger CI unit tests
git checkout -b dev
git commit "create dev branch"
git push

# create a beta branch to trigger CI hockeyapp distribution
git checkout -b beta
git commit "create beta branch"
git push

# create a release branch to trigger CI codepush distribution
git checkout -b release
git commit "create release branch"
git push

# respect the proper code flow; start in the dev branch
git checkout dev
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.

Give each branch a custom config.xml

In a “normal” local development environment, Cordova build depends on a config.xml 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 /config folder with XML and JS files for each release type (i.e. dev, beta and release). Let’s compare a few lines from dev.xml and beta.xml to see what’s different:

Switch the Code Push Deployment Key

This code snippet from /config/dev.xml shows where we declare the Code Push deployment key.
<platform name="android">
    <preference name="CodePushDeploymentKey" value="YOUR-ANDROID-DEVELOPMENT-DEPLOYMENT-KEY" />
</platform>
<platform name="ios">
    <preference name="CodePushDeploymentKey" value="YOUR-IOS-DEVELOPMENT-DEPLOYMENT-KEY" />
</platform>
We don’t want to use the same key in both development and production, so we’ll use a different key in /config/release.xml
<platform name="android">
    <preference name="CodePushDeploymentKey" value="YOUR-ANDROID-PRODUCTION-DEPLOYMENT-KEY" />
</platform>
<platform name="ios">
    <preference name="CodePushDeploymentKey" value="YOUR-IOS-PRODUCTION-DEPLOYMENT-KEY" />
</platform>

Give each branch a custom Gulp task

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, gulp release will:
  1. Rename release.xml to config.xml and move it to the project root
  2. Rename release.js to config.js and move it to /www/js/
The ✨magic✨ happens in gulpfile.js:
// prepare for public release
gulp.task('release', function() {
    gulp.src('./config/release.xml')
    .pipe(rename('config.xml'))
    .pipe(gulp.dest('.'));

    gulp.src('./config/release.js')
    .pipe(rename('config.js'))
    .pipe(gulp.dest('./www/js'));
});

// prepare for beta distribution
gulp.task('beta', function() {
    gulp.src('./config/beta.xml')
    .pipe(rename('config.xml'))
    .pipe(gulp.dest('.'));

    gulp.src('./config/beta.js')
    .pipe(rename('config.js'))
    .pipe(gulp.dest('./www/js'));
});

// prepare for development environment
gulp.task('dev', function() {
    gulp.src('./config/dev.xml')
    .pipe(rename('config.xml'))
    .pipe(gulp.dest('.'));

    gulp.src('./config/dev.js')
    .pipe(rename('config.js'))
    .pipe(gulp.dest('./www/js'));
});
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:
  1. Change your app icon and splash screen to display a “beta” badge only in the beta release
  2. Use a different API key for services like maps, analytics, data sync, etc. in production
  3. Show features only in development builds
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 “Street Lights for a Ribcage” by Sleepy Eyes of Death.

Configure VSTS

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.
Using the same instance of VSTS that you configured in Parts III and III of this series, login and navigate to the “BUILD” tab where you will edit the “Android-Dev” build definition.
Add a space-delimited array of Gulp tasks for dev sass test. In order, these will:
  1. dev rename and moves /config/dev.xml to /config.xml
  2. sass compile SASS to CSS
  3. test execute our Karma+Jasmine tests
Configure your Gulp Task build step
Under the “Triggers” tab, change continuous integration to trigger when code changes in the devbranch.
Configure your trigger
Under the “Prepository” tab, change the default branch to use dev branch as the source repository.
Configure your source repository
“Android-Beta” and “Android-Release” will follow a similar pattern. You can see a full list of the values below.
Build DefinitionGulp TasksSource RepositoryTrigger Filter
Android-Devdev sass testDevDev
Android-Betabeta sassBetaBeta
Android-Releaserelease sassReleaseRelease

Checking Our Work

You’ve done all the hard work, now pluck the fruit of your labors. 🍊 In Terminal:
# trigger CI for "Android-Dev" by pushing a change
git checkout dev
git touch readme.md
git commit -m "triggering dev CI"
git push

# trigger CI for "Android-Beta" by pushing a change
git checkout beta
git merge dev
git push

# trigger CI for "Android-Release" by pushing a change
git checkout release
git merge beta
git push
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 “The Commander Thinks Aloud” by The Long Winters.

Release Without Re-submitting to the App Store


This is part three of a four part series and assumes you’ve already set-up continuous integration with our sample project. If you haven’t already, I highly recommend reading part one, Continuous Integration for Cordova Apps, where we setup the build server used in this tutorial.

JavaScript’s Unfair Advantage

Mobile apps created with JavaScript (e.g. Apache CordovaReactNative) 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.
Imagine this: 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. 🔥
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.

How does it work?

To understand how updates work, it’s helpful to get a high-level overview of the workflow:
  1. The first time you publish, you submit the full app including both compiled code and web assets to the app store.
  2. 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.
  3. On some event (e.g. deviceready, resume, button click), each mobile device checks the Code-Push cloud service for an update.
  4. If an update is available, each mobile device downloads the update and replaces the contents of /www with your new code.
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 /www, or (3) release to only a subset of users. The service itself is very configurable.

Is Apple really cool with it?

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.
Sample Apple Developer Agreement

Service Providers

There are a number of plugins & services that make over-the-air updates possible. Cordova developers can choose between PhoneGap HydrationIonic Deploy and Code Push. Code Push also happens to be the defacto choice for most ReactNative developers, so our example will use it.

Let’s code (push)

Again, our tutorial will use the superfly sample project and Visual Studio Team Services (VSTS) setup in part one of this series.
Like other device capabilities, Code Push has it’s own Cordova plugin (cordova-plugin-code-push)which has already been added to our sample project. You can see the reference in config.xml:
<plugin name="cordova-plugin-code-push" spec="~1.6.0-beta" />

Create Your Code-Push Service

To register for the Code Push service, you must first install the Code Push CLI. From your terminal…
# install code-push globally
npm install -g code-push-cli

# sign up for the free code-push service
code-push register
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. 💸

Manage Deployments

Once you’ve registered, go back to the command line where we’ll register our first app with the Code Push service.
code-push app add superfly
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 config.xml.If you lose the key, don’t worry. You can retrieve it at any time by executing:
code-push deployment list superfly -k
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.
# rename the default Staging key for Android
code-push deployment rename superfly Staging Staging-Android

# rename the default Production key for Android
code-push deployment rename superfly Production Production-Android

# create a new Staging key for iOS
code-push deployment add superfly Staging-iOS

# create a new Production key for iOS
code-push deployment add superfly Production-iOS

# make sure we got it right
code-push deployment list superfly -k

Add the Deployment Key to Your App

You’ve created deployment keys for each platform. Now, it’s time to save those deployment keys with your app. Open config.xml in the superfly project and insert the Staging-Android and Staging-iOS deployment keys where indicated:
<platform name="android">
    <preference name="CodePushDeploymentKey" value="YOUR-ANDROID-DEPLOYMENT-KEY" />
</platform>
<platform name="ios">
    <preference name="CodePushDeploymentKey" value="YOUR-IOS-DEPLOYMENT-KEY" />
</platform>
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).

Safety first!

Any Cordova app using version 5 or greater is required to use the whitelist plugin and declare a Content Security Policy (CSP). 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 index.html:
<meta http-equiv="Content-Security-Policy" content="script-src https://codepush.azurewebsites.net http://localhost:* 'self' 'unsafe-inline' 'unsafe-eval'; media-src *">   
Translated into English this CSP says, “It’s okay to execute JavaScript from…”
👍 https://codepush.azurewebsites.net
👍 http://localhost
👍 Any file referenced in root directory
👍 Inline <script> tags
👍 eval() statements are also OK
👍 Images, videos and other media can come from anywhere

Invoke the Code-Push API

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 deviceready. Our button can be found in /www/templates/menu.html.
<!-- Code Push -->
<a class="item item-icon-left" ng-click="codePush()">
    <i class="icon ion-ios-cloud-download-outline"></i>Check Update
</a>
… and the handler in /www/js/controllers.js
/* ---------------------------------------------------------------
   codepush check for update */
$scope.codePush = function() {
    console.log('check for code push update');
    
    var updateDialogOptions = {
        updateTitle: "Update",
        mandatoryUpdateMessage: "You will be updated to the latest version of the app.",
        mandatoryContinueButtonLabel: "Continue",
        optionalUpdateMessage: "Update available. Install?",
        optionalIgnoreButtonLabel: "No",
        optionalInstallButtonLabel: "Yes",
    };

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

            // Intermediate (non final) statuses
            case SyncStatus.CHECKING_FOR_UPDATE:
                console.log("Checking for update.");
                break;
            case SyncStatus.AWAITING_USER_ACTION:
                console.log("Alerting user.");
                break;
            case SyncStatus.DOWNLOADING_PACKAGE:
                console.log("Downloading package.");
                break;
            case SyncStatus.INSTALLING_UPDATE:
                console.log("Installing update");
                break;
        }
    };

    window.codePush.sync(syncStatusCallback, syncOptions);
}
// --------------------------------------------------------------- */
While this looks complicated, it’s really quite simple once you break it down.
  1. updateDialogOptions {...} provides optional text and styling information
  2. syncOptions {...} tells Code Push to replace the content of /www on restart
  3. syncStatusCallback handles the range of possible responses from the Code-Push service
  4. window.codePush.sync(...) invokes the actual codePush API
The truth is that you could omit items 1-3 and just let Code Push do everything with window.codePush.sync(), but it’s nice to see what’s possible with a fully customized implementation.

Automate Deployments with VSTS

Similar to the steps followed in part two of this series, we’re going to clone our pre-existing build definition and add a Code Push build step. Login to VSTS and navigate to the BUILD tab. Right-click on the “Android-Build” definition and select Clone.
Clone build definition
With the new build definition selected, click “Add build step” and choose “Code Push - Release” from the Deploy category.
Add build step: Code Push - Release

Configure Your Code Push Build Step

Once you’ve added the build step, you’ll need to configure it as pictured below:
Configure Code Push build step
LabelValueWhy?
Authentication MethodAccess KeyEstablishes trust with the Code Push cloud service
Access Key$(code-push-access-key)To protect our access key, we’ll encrypt it in a variable
App NamesuperflyThis is human readable app name published to the store
DeploymentStagingWe’ll use staging while testing
Update Contents Pathplatforms/android/assets/wwwDefault location where Cordova outputs the web assets for Android
Target Binary Version^0.0.2Semantic Versioning. This says, “install the update if you’re installed app is of version 0.0.2+n”
Rollout100%Deploy this update to 100% of possible users
Descrition You can display this message to users before an update
MandatoryUncheckedDon’t require this update
DisabledUncheckedWe want to run this build step
EnabledCheckedWe want to run this build step
Continue on errorUncheckedStop the presses when there’s a build error
Always runUncheckedSince we never continue on error, it’s okay to leave this unchecked

Establish Trust Between VSTS and Code Push

As you probably noticed above, we referenced a VSTS build variable to store $(code-push-access-key), 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:
# create the access key
code-push access-key add superfly
This command will return a 32 character key. Back in VSTS, go to the Variables 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.
Add the code-push-access-key variable

Deploy on Commit

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.”
Triggers set to use Continuous Integration
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…
git commit -m "Add code push"
git push
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”.
Android-Release build executing

Open the App and Check for Updates

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…
cd ~/Code/superfly
cordova run android --device
From the running app, open the slide-in menu and select “Code Push Update”
App running on Android
TA-DA! Your app will update on restart.

Beta Test with HockeyApp


This is part two of a four part series and assumes you’ve already set-up continuous integration with our sample project. If you haven’t already, I highly recommend reading Continuous Integration for Cordova Apps.
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.”
For beta testing, I prefer HockeyApp over Apple TestFlight or the Google beta program because it allows me to distribute to unlimited beta testers, collect crash analytics and user feedback.
With HockeyApp, I can:
  • Upload my apps manually through the website or through a CI system
  • Distribute to a controlled group of iOS, Android and Windows users
  • Collect detailed crash reporting and analytics
  • Collect user feedback (e.g. send messages, attach screenshots, threaded communication)
  • Control the app version installed on user devices
  • Integrate with most popular bug tracking systems
HockeyApp is also free for accounts with up to 2 apps, so it’s a no-brainer to try.

Get Started

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 http://www.hockeyapp.net.
Signup for a HockeyApp account
Once you’ve created an account, we need to establish trust with VSTS by sharing an API Token. Go to Account Settings within the HockeyApp portal and select API Tokens 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.
Create an API token
Switch to a new browser tab and open the Build tab of your VSTS account. Right-click on the Android-Build definition that you previously created and select Clone. After all, there’s no need to start from scratch again. In the cloned build definition, add the HockeyApp build step after “Cordova Build”.
Add Hockey App Build Step
To get the HockeyApp Connection, you’ll need to take one extra step. Click Manage adjacent to the input box. In the new window, select HockeyApp, then enter the 32-character token you created in HockeyApp.
Add Service Endpoint for HockeyApp
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:
Configure your HockeyApp build step
LabelValueWhy?
HockeyApp ConnectionAll AppsPoints to the API token you created in HockeyApp and establishes trust
App IDcom.ryanjsalva.superflyThis is the default App ID of my sample project. You can find/change it by looking for the <widget> tag in config.xml
Binary File Pathplatforms/android/build/outputs/apk/android-debug.apkDefault location where Cordova outputs the native app package
Symbols File Path Not used by our project
Native Library File Path Not used by our project
Release Notes (File) Not used by our project
Release NotesRecommended update.Because if we say it’s “recommended”, surely they’ll install it, right?
PublishCheckedImmediately publishes to HockeyApp
MandatoryUncheckedNot a required update
Notify Users?CheckedUsers are more likely to install if you tell them something is available
Tags No tags mean the update goes to everyone
Teams No teams mean the update goes to everyone
Users No users mean the udpate goes to everyone
EnabledCheckedWe want to run this build step
Continue on errorUncheckedStop the presses when there’s a build error
Always runUncheckedSince we never continue on error, it’s okay to leave this unchecked
Save this build definition as Android-Beta. 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 The Modern Lovers’ Girlfriend.

Invoke HockeyApp from Code

The HockeyApp SDK is delivered as a Cordova plugin. That plugin is already installed in our sample project (you’re welcome), but we need to invoke it. Fortunately, that only requires modifying a few lines. In /www/js/app.js, uncomment the following line and replace “APP_ID” with the value for <widget id="APP_ID"> found in config.xml. Assuming you don’t create your own APP_ID, the value will be “com.ryanjsalva.superfly”.
hockeyapp.start(null, null, "APP_ID");
 
Note that the APP_ID needs to match in three places:
LocationValueWhy?
/config.xml<widget id="APP_ID">Authoritative source for APP_ID
/www/js/app.jshockeyapp.start(null,null,"APP_ID")Establishes app identity when calling into the HockeyApp API
VSTSHockeyApp build step configurationI honestly don’t know
Next, we need to uncomment a few lines from /www/js/controllers.js
$scope.fakeCrash = function() {
    console.log('fake crash');
    hockeyapp.addMetaData(null, null, { someCustomProp: 23, anotherProp: "Custom Value" });
    hockeyapp.forceCrash();
}

// hockeyapp send feedback
$scope.sendFeedback = function() {
    console.log('send feedback');
    hockeyapp.feedback();
}

// hockeyapp check for update
$scope.checkForUpdate = function() {
    console.log('check for HockeyApp update');
    hockeyapp.trackEvent(null, null, 'Check for Update');
    hockeyapp.checkForUpdate();
}
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 hockeyapp.addMetaData([json]). You can invoke this method at any time to record nparameters describing your runtime state. This can be tremendously helpful if you need help debugging you app in the wild.

Run that build, you crazy kid

Now, you’re ready to automate your next build with another commit. Go back to Terminal and run:
git commit -m "enabled hockeyapp"
git push
When you return to VSTS, you should find a build in the queue for Android-Beta.

Install HockeyApp on your Android Device

While that’s building, grab your Android phone and install HockeyApp. HockeyApp is also available for iOS and Windows, but this tutorial only builds for Android… so, hopefully, you’ve got an Android phone handy 😛
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:
  1. Fake Crash will force the mobile app to crash so you have a chance to explore HockeyApp’s crash analytics
  2. Send Feedback 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.
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 “March of the Nucleotides by Bit Shifter.”

Continuous Integration for Cordova Apps


This is part one of a four part series focused on devops for mobile apps. It assumes you’ve already installed NVMNodeNPMGitGulpCordova and Ionic. I also recommend using VS Code for your code editor, but use whatever’s comfortable. All steps work equally well on Mac and Windows.

Why Continuous Integration?

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:
  1. Run unit, functional or UI tests to measure code quality
  2. Notify other team members that a build is available
  3. Distribute your app to beta testers
  4. Publish your app to the store
Imagine this: 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.
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.
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.”
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.
Over the next four blog posts we’ll…
  1. Setup continuous integration
  2. Distribute our app to beta testers for manual testing
  3. By-pass the app store to release bug fixes
  4. Customize our build steps to use a different config for each release type – i.e. dev, beta, release.

Get the Sample Project

Start by cloning the sample project into your local directory and restoring all the npm packages
git clone http://github.com/ryanjsalva/superfly
cd superfly
npm install

Create Your Project in VSTS

Visual Studio Team Services (VSTS) provides three basic services:
  1. Git-based source control
  2. Cross-platform build automation
  3. Agile team management (e.g. scrum boards, backlogs, bug tracking)
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:
VSTS New Project
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.
Create a new project and name it superfly. 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.
git remote add origin https://ryanjsalva.visualstudio.com/DefaultCollection/_git/superfly
git commit -m "first commit"
git push
Congratulations! You’ve setup your dev environment and source control. Celebrate with an awesome song on your headphones. I recommend anything from Girl Talk’s Feed the Animals.

Let’s Get Unit Tests Running

Like most Angular projects, our sample app uses Karma and Jasmine to run unit tests found in the /tests directory. Our sample project includes a Gulp task to invoke these tests.
gulp.task('test', function (done) {
  new Server({
    configFile: __dirname + '/tests/karma.conf.js',
    singleRun: true
  }, done).start();
});
If you’re not already familiar with Gulp, 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 gulp test
This task runs our unit tests in a headless browser called PhantomJS. If everything worked correctly, you should see the following response:
Using gulpfile ~/Desktop/superfly/gulpfile.js
Starting 'test'...
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 (Mac OS X 0.0.0): Connected on socket /#WzMqeZ4EfOqhVS27AAAA with id 41819505
PhantomJS 1.9.8 (Mac OS X 0.0.0): Executed 2 of 2 SUCCESS (0.007 secs / 0.012 secs)
Finished 'test' after 1.4 s

Run Tests on Every Push

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.
VSTS Empty list of build defitions
Name your build definition “Android-Build”, choose the Empty template and select the Continuous Integration checkbox since we want builds to run on every push. Otherwise, stick with the defaults.
VSTS New Build Definition
Each build definition is comprised of multiple build steps executed in serial order. Let’s add some. Click Add build step and add the following steps in this order:
  1. npm: to download all the node_modules not saved in our repository
  2. Gulp: to run our “test” task
  3. Cordova Build: to build the app
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:
VSTS Build Step: npmVSTS Build Step: gulpVSTS Build Step: Cordova Build
Under the Cordova Build step, notice the $(configuration) 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 Variables tab and add a new variable for “configuration” before clicking SAVE.
VSTS Build Definition: Variables
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 Triggers tab, we see that new builds will be triggered every time there’s a push to the master branch.
VSTS Build Definition: Triggers
Under the Repository tab, we see that new builds will also use source from the master branch.
VSTS Build Definition: Repository
If you haven’t already, click SAVE so that we can trigger our first CI build. Go back to terminal, and commit a change:
touch readme.md
git commit -m "created empty readme"
git push
You can see your build spool by clicking Builds, then navigating to the Queued tab and double-clicking the queued build.
When it’s all done, you should see a build progress report declaring success. Celebrate with another song in your headphones. I recommend “I Don’t Want to Get Over You” by the Magnetic Fields.

The best ways to connect to the server using Angular CLI

Everybody who has used  Angular CLI  knows that it is a powerful tool which can take a front-end development job to a completely dif...