Skip to content

Instantly share code, notes, and snippets.

@robdmoore
Last active April 12, 2018 08:57
Show Gist options
  • Save robdmoore/c09cb5f8aa7682f195c1 to your computer and use it in GitHub Desktop.
Save robdmoore/c09cb5f8aa7682f195c1 to your computer and use it in GitHub Desktop.
NodeJS Script to automate deployment of Cordova/PhoneGap application using PhoneGapBuild

pgb-wrapper.js is a generic wrapper module around the phonegap-build-api NodeJS package to add promise support and some helpful functions to make it easier to create a build script.

pgb.example.js is one example of such a build script, but you might decide to do it differently.

You can execute pgb.example.js by:

node pgb.example.js --appId {PGB_APP_ID} --apiKey {PGB_API_KEY} --zipFile {path/to/deployment_file.zip} --iosOverTheAirKey {NAME_OF_IOS_OVER_THE_AIR_KEY_IN_PGB} --iosOverTheAirKeyPassword {PASSWORD_OF_IOS_OVER_THE_AIR_KEY_IN_PGB} --iosKey {NAME_OF_IOS_PRODUCTION_KEY_IN_PGB} --iosKeyPassword {PASSWORD_OF_IOS_PRODUCTION_KEY_IN_PGB} --androidKey {NAME_OF_ANDROID_PRODUCTION_KEY_IN_PGB} --androidKeyPassword {PASSWORD_OF_ANDROID_PRODUCTION_KEY_IN_PGB}

The following node packages need to be installed for it all to work:

  • phonegap-build-api
  • q
  • minimist

This script has been published via my blog post.

// Wrapper module around phonegap-build-api NodeJS package that provides a
// promise-enabled set of commands for creating a PhoneGapBuild build script
// References:
// http://docs.build.phonegap.com/en_US/developer_api_api.md.html
// https://www.npmjs.org/package/phonegap-build-api
module.exports = function(api) {
var $q = require("q");
var fs = require("fs");
var getFormData = function(data, file) {
var fd = {
form: {
data: data
}
};
if (file) {
fd.form.file = file;
}
return fd;
};
var getKeyId = function(type, title) {
console.log("Getting key id: " + type + "/" + title);
var q = $q.defer();
api.get("/keys/" + type, function(e, data) {
if (e) {
q.reject(e);
return;
}
var found = false;
data.keys.forEach(function(key) {
if (key.title == title) {
console.log("Found key id: " + type + "/" + title + " - " + key.id);
q.resolve(key.id);
found = true;
}
});
if (found) {
return;
}
q.reject("Couldn't find " + type + "/" + title + "; given: "
+ data.keys.reduce(function(prev, curr) {
if (prev) {
prev = prev + ", ";
}
prev += curr.title
}, "")
);
});
return q.promise;
};
var unlockIosKey = function(id, password) {
console.log("Unlocking ios key: " + id);
var q = $q.defer();
api.put("/keys/ios/"+id, getFormData({password: password}), function(e, data) {
if (e) {
q.reject(e);
return;
}
q.resolve();
});
return q.promise;
};
var unlockAndroidKey = function(id, password) {
console.log("Unlocking android key: " + id);
var q = $q.defer();
api.put("/keys/android/"+id, getFormData({key_pw: password, keystore_pw: password}), function(e, data) {
if (e) {
q.reject(e);
return;
}
q.resolve();
});
return q.promise;
};
var uploadApp = function(zipFile, id) {
console.log("Uploading app id " + id + " from " + zipFile);
var q = $q.defer();
api.put("/apps/" + id, getFormData({debug: false}, zipFile), function(e, data) {
if (e) {
q.reject(e);
return;
}
console.log("Successfully uploaded app id " + id);
q.resolve(data);
});
return q.promise;
};
var setAppKeys = function(id, keys) {
console.log("Setting app keys for app id: " + id);
console.log(keys);
var q = $q.defer();
api.put("/apps/" + id, getFormData({keys:keys}), function(e, data) {
if (e) {
q.reject(e);
return;
}
console.log("Set app keys for app id: " + id);
q.resolve();
});
return q.promise;
};
var waitForBuild = function(id, type) {
console.log("Waiting for build of app id " + id + " type " + type);
var q = $q.defer();
var checkStatus = function() {
api.get("/apps/" + id, function(e, data) {
if (e) {
q.reject(e);
return;
}
var buildStatus = data.status[type];
console.log("Current build status for app id " + id + " type " + type + " is " + buildStatus);
if (buildStatus == "complete") {
q.resolve();
return;
}
if (buildStatus == "error") {
q.reject("Build for app id " + id + " type " + type + " is error");
return;
}
setTimeout(checkStatus, 300);
});
};
checkStatus();
return q.promise;
};
var downloadAppBuild = function(id, type) {
console.log("Downloading " + type + " build for app id: " + id);
var fileNames = {
ios: "build.ipa",
android: "build.apk",
winphone: "build.xap"
};
var fileName = fileNames[type];
if (!fileName) {
return $q.reject("Unknown build type: " + type);
}
var q = $q.defer();
api.get("/apps/" + id + "/" + type)
.on("response", function(res) {
res.pipe(fs.createWriteStream(fileName))
.on("close", function() {
console.log("Downloaded " + type + " build for app id: " + id + " to " + fileName);
q.resolve(fileName);
})
.on("error", function() {
q.reject("Failed to download the " + type + " build for app id: " + id);
});
})
.on("error", function(e) {
q.reject(e);
});
return q.promise;
};
return {
getKeyId: getKeyId,
unlockIosKey: unlockIosKey,
unlockAndroidKey: unlockAndroidKey,
uploadApp: uploadApp,
setAppKeys: setAppKeys,
waitForBuild: waitForBuild,
downloadAppBuild: downloadAppBuild
};
};
var argv = require('minimist')(process.argv.slice(2));
var appId = argv.appId;
var apiKey = argv.apiKey;
var zipFile = argv.zipFile;
var iosOverTheAirKeyTitle = argv.iosOverTheAirKey;
var iosOverTheAirKeyPassword = argv.iosOverTheAirKeyPassword;
var iosKeyTitle = argv.iosKey;
var iosKeyPassword = argv.iosKeyPassword;
var androidKeyTitle = argv.androidKey;
var androidKeyPassword = argv.androidKeyPassword;
var client = require("phonegap-build-api");
client.auth({ token: apiKey }, function(e, api) {
if (e) {
throw e;
}
var pgb = require("./pgb-wrapper")(api);
var $q = require("q");
var androidKeyId, iosKeyId, iosOverTheAirKeyId;
$q.all([
// Unlock android
pgb.getKeyId("android", androidKeyTitle)
.then(function(id) {
androidKeyId = id;
return pgb.unlockAndroidKey(id, androidKeyPassword);
}),
// Unlock ios
pgb.getKeyId("ios", iosKeyTitle)
.then(function(id) {
iosKeyId = id;
return pgb.unlockIosKey(id, iosKeyPassword);
}),
// Unlock ios over-the-air
pgb.getKeyId("ios", iosOverTheAirKeyTitle)
.then(function(id) {
iosOverTheAirKeyId = id;
return pgb.unlockIosKey(id, iosOverTheAirKeyPassword);
})
])
// Set which signing keys to use
.then(function() {
return pgb.setAppKeys(appId, {"ios": iosOverTheAirKeyId, "android": androidKeyId});
})
// Build the zip
.then(function() {
return pgb.uploadApp(zipFile, appId);
})
// Wait for the builds to complete
.then(function() {
return $q.all([
pgb.waitForBuild(appId, "ios"),
pgb.waitForBuild(appId, "android")
]);
})
// Download the ios OverTheAir build file - upload this somewhere with appropriate plist for Beta-testing iOS
.then(function() {
return pgb.downloadAppBuild(appId, "ios");
})
// Change ios to use production app store key - also causes a build
.then(function() {
return pgb.setAppKeys(appId, {"ios": iosKeyId, "android": androidKeyId});
})
// Wait for the builds to complete
.then(function() {
return $q.all([
pgb.waitForBuild(appId, "ios"),
pgb.waitForBuild(appId, "android")
]);
})
.then(function(){
console.log("Done.");
})
.catch(function(e){
console.log("Error:");
console.log(e);
process.exit(1); // There is a bug where sometimes the log above doesn't flush before this exits
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment