Merge pull request #96 from Microsoft/mandatoryInstallMode

Test improvements and mandatory install mode
This commit is contained in:
scottbommarito 2016-04-19 15:51:11 -07:00
Родитель d5f3b05ef8 ae51f5dd86
Коммит ae66e60158
20 изменённых файлов: 724 добавлений и 299 удалений

Просмотреть файл

@ -373,6 +373,8 @@ While the `sync` method tries to make it easy to perform silent and active updat
- __installMode__ *(InstallMode)* - Used to specify the [InstallMode](#installmode) used for the install operation. Defaults to `InstallMode.ON_NEXT_RESTART`.
- __mandatoryInstallMode__ *(InstallMode)* - Used to specify the [InstallMode](#installmode) used for the install operation if the package is mandatory. Defaults to `InstallMode.IMMEDIATE`.
- __minimumBackgroundDuration__: If __installMode__ is `InstallMode.ON_NEXT_RESUME`, used to specify the amount of time the app must be in the background before the update is installed when it is resumed. Defaults to `0`.
- __ignoreFailedUpdates__ *(Boolean)* - Optional boolean flag. If set, updates available on the server for which and update was attempted and rolled back will be ignored. Defaults to `true`.
@ -577,6 +579,8 @@ Otherwise, the install operation will be marked as failed, and the application i
- __installMode__: Used to specify the [InstallMode](#installmode) used for the install operation. Defaults to `InstallMode.ON_NEXT_RESTART`.
- __mandatoryInstallMode__: Used to specify the [InstallMode](#installmode) used for the install operation if the package is mandatory. Defaults to `InstallMode.IMMEDIATE`.
- __minimumBackgroundDuration__: If __installMode__ is `InstallMode.ON_NEXT_RESUME`, used to specify the amount of time the app must be in the background before the update is installed when it is resumed. Defaults to `0`.
Example Usage:
@ -593,8 +597,9 @@ var onInstallSuccess = function () {
};
var onPackageDownloaded = function (localPackage) {
// Install the update after someone navigates away from the app for more than 2 minutes
localPackage.install(onInstallSuccess, onError, { installMode: InstallMode.ON_NEXT_RESUME, minimumBackgroundDuration: 120 });
// Install regular updates after someone navigates away from the app for more than 2 minutes
// Install mandatory updates after someone restarts the app
localPackage.install(onInstallSuccess, onError, { installMode: InstallMode.ON_NEXT_RESUME, minimumBackgroundDuration: 120, mandatoryInstallMode: InstallMode.ON_NEXT_RESTART });
};
var onUpdateCheck = function (remotePackage) {
@ -620,7 +625,7 @@ window.codePush.checkForUpdate(onUpdateCheck, onError);
var app = {
onDeviceReady: function () {
// Calling this function is requirede during the first application run after an update.
// Calling this function is required during the first application run after an update.
// If not called, the application will be reverted to the previous version.
window.codePush.notifyApplicationReady();
// ...

Просмотреть файл

@ -255,6 +255,7 @@ var CodePush = (function () {
ignoreFailedUpdates: true,
installMode: InstallMode.ON_NEXT_RESTART,
minimumBackgroundDuration: 0,
mandatoryInstallMode: InstallMode.IMMEDIATE,
updateDialog: false,
deploymentKey: undefined
};

Просмотреть файл

@ -90,12 +90,15 @@ var LocalPackage = (function (_super) {
else {
var invokeSuccessAndInstall = function () {
CodePushUtil.logMessage("Install succeeded.");
if (installOptions.installMode === InstallMode.IMMEDIATE) {
var installModeToUse = _this.isMandatory ? installOptions.mandatoryInstallMode : installOptions.installMode;
if (installModeToUse === InstallMode.IMMEDIATE) {
installSuccess && installSuccess();
cordova.exec(function () { }, function () { }, "CodePush", "install", [deployDir.fullPath, installOptions.installMode.toString(), installOptions.minimumBackgroundDuration.toString()]);
cordova.exec(function () { }, function () { }, "CodePush", "install", [deployDir.fullPath,
installModeToUse.toString(), installOptions.minimumBackgroundDuration.toString()]);
}
else {
cordova.exec(function () { installSuccess && installSuccess(); }, function () { installError && installError(); }, "CodePush", "install", [deployDir.fullPath, installOptions.installMode.toString(), installOptions.minimumBackgroundDuration.toString()]);
cordova.exec(function () { installSuccess && installSuccess(); }, function () { installError && installError(); }, "CodePush", "install", [deployDir.fullPath,
installModeToUse.toString(), installOptions.minimumBackgroundDuration.toString()]);
}
};
var preInstallSuccess = function () {
@ -355,7 +358,8 @@ var LocalPackage = (function (_super) {
if (!LocalPackage.DefaultInstallOptions) {
LocalPackage.DefaultInstallOptions = {
installMode: InstallMode.ON_NEXT_RESTART,
minimumBackgroundDuration: 0
minimumBackgroundDuration: 0,
mandatoryInstallMode: InstallMode.IMMEDIATE
};
}
return LocalPackage.DefaultInstallOptions;

Просмотреть файл

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="cordova-plugin-code-push" version="1.6.0-beta">
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="cordova-plugin-code-push" version="1.7.0-beta">
<name>CodePush</name>
<description>This plugin allows you to push code updates to your apps instantly using the CodePush service</description>
<license>MIT</license>
@ -7,8 +7,8 @@
<repo>https://github.com/Microsoft/cordova-plugin-code-push.git</repo>
<dependency id="code-push" version=">=1.8.0-beta" />
<dependency id="cordova-plugin-file-transfer" version=">=1.3.0" />
<dependency id="cordova-plugin-file" version=">=3.0.0" />
<dependency id="cordova-plugin-file" version=">=3.0.0" />
<dependency id="cordova-plugin-file-transfer" version=">=1.3.0" />
<dependency id="cordova-plugin-zip" version=">=3.0.0" />
<dependency id="cordova-plugin-dialogs" version=">=1.1.1" />
<dependency id="cordova-plugin-device" version=">=1.1.0" />

Просмотреть файл

@ -41,17 +41,27 @@ export interface IEmulatorManager {
/**
* Launches an already installed application by app id.
*/
launchInstalledApplication(appId: string): Q.Promise<void>;
launchInstalledApplication(appId: string): Q.Promise<string>;
/**
* Ends a running application given its app id.
*/
endRunningApplication(appId: string): Q.Promise<string>;
/**
* Restarts an already installed application by app id.
*/
restartApplication(appId: string): Q.Promise<void>;
restartApplication(appId: string): Q.Promise<string>;
/**
* Navigates away from the current app, waits for a delay, then navigates to the specified app.
* Navigates away from the current app, waits for a delay (defaults to 1 second), then navigates to the specified app.
*/
resumeApplication(appId: string, delayBeforeResumingMs: number): Q.Promise<void>;
resumeApplication(appId: string, delayBeforeResumingMs: number): Q.Promise<string>;
/**
* Prepares the emulator for a test.
*/
prepareEmulatorForTest(appId: string): Q.Promise<string>;
}
/**
@ -130,17 +140,17 @@ export class IOSEmulatorManager implements IEmulatorManager {
/**
* Launches an already installed application by app id.
*/
public launchInstalledApplication(appId: string): Q.Promise<void> {
return tu.TestUtil.getProcessOutput("xcrun simctl launch booted " + appId, undefined, true)
.then<void>(output => { console.log(output); });
launchInstalledApplication(appId: string): Q.Promise<string> {
return tu.TestUtil.getProcessOutput("xcrun simctl launch booted " + appId, undefined);
}
/**
* Ends a running application, given its Cordova app id.
* Ends a running application given its app id.
*/
private endRunningApplication(appId: string): Q.Promise<void> {
return tu.TestUtil.getProcessOutput("xcrun simctl spawn booted launchctl list", undefined, true)
endRunningApplication(appId: string): Q.Promise<string> {
return tu.TestUtil.getProcessOutput("xcrun simctl spawn booted launchctl list", undefined)
.then<string>(processListOutput => {
// find the app's process
var regex = new RegExp("(\\S+" + appId + "\\S+)");
var execResult: any[] = regex.exec(processListOutput);
if (execResult) {
@ -151,68 +161,102 @@ export class IOSEmulatorManager implements IEmulatorManager {
}
})
.then<string>(applicationLabel => {
return tu.TestUtil.getProcessOutput("xcrun simctl spawn booted launchctl stop " + applicationLabel, undefined, true);
})
.then<void>(output => {
console.log(output);
// kill the app if we found the process
return tu.TestUtil.getProcessOutput("xcrun simctl spawn booted launchctl stop " + applicationLabel, undefined);
}, (error) => {
// we couldn't find the app's process so it must not be running
return Q.resolve(error);
});
}
/**
* Restarts an already installed application by app id.
*/
restartApplication(appId: string): Q.Promise<void> {
restartApplication(appId: string): Q.Promise<string> {
return this.endRunningApplication(appId)
.then<void>(() => {
// wait for a second before restarting
return Q.delay(1000);
})
.then(() => this.launchInstalledApplication(appId));
}
/**
* Navigates away from the current app, waits for a delay, then navigates to the specified app.
* Navigates away from the current app, waits for a delay (defaults to 1 second), then navigates to the specified app.
*/
resumeApplication(appId: string, delayBeforeResumingMs: number): Q.Promise<void> {
resumeApplication(appId: string, delayBeforeResumingMs: number = 1000): Q.Promise<string> {
// open a default iOS app (for example, camera)
return this.launchInstalledApplication("com.apple.camera")
.then<void>(() => {
console.log("Waiting for " + delayBeforeResumingMs + "ms before resuming the test application.");
return Q.delay(delayBeforeResumingMs);
})
.then<void>(() => {
.then<string>(() => {
// reopen the app
return this.launchInstalledApplication(appId);
});
}
/**
* Prepares the emulator for a test.
*/
prepareEmulatorForTest(appId: string): Q.Promise<string> {
return this.endRunningApplication(appId);
}
}
export class AndroidEmulatorManager implements IEmulatorManager {
/**
* Launches an already installed application by app id.
*/
public launchInstalledApplication(appId: string): Q.Promise<void> {
return ProjectManager.ProjectManager.execAndLogChildProcess("adb shell monkey -p " + appId + " -c android.intent.category.LAUNCHER 1");
launchInstalledApplication(appId: string): Q.Promise<string> {
return ProjectManager.ProjectManager.execChildProcess("adb shell monkey -p " + appId + " -c android.intent.category.LAUNCHER 1");
}
/**
* Ends a running application given its app id.
*/
endRunningApplication(appId: string): Q.Promise<string> {
return ProjectManager.ProjectManager.execChildProcess("adb shell am force-stop " + appId);
}
/**
* Restarts an already installed application by app id.
*/
restartApplication(appId: string): Q.Promise<void> {
return ProjectManager.ProjectManager.runPlatform(tu.TestUtil.testRunDirectory, Android.getInstance(), true, tu.TestUtil.readTargetEmulator());
restartApplication(appId: string): Q.Promise<string> {
return this.endRunningApplication(appId)
.then<void>(() => {
// wait for a second before restarting
return Q.delay(1000);
})
.then<string>(() => {
return this.launchInstalledApplication(appId);
});
}
/**
* Navigates away from the current app, waits for a delay, then navigates to the specified app.
* Navigates away from the current app, waits for a delay (defaults to 1 second), then navigates to the specified app.
*/
resumeApplication(appId: string, delayBeforeResumingMs: number): Q.Promise<void> {
resumeApplication(appId: string, delayBeforeResumingMs: number = 1000): Q.Promise<string> {
// open a default Android app (for example, settings)
return this.launchInstalledApplication("com.android.settings")
.then<void>(() => {
console.log("Waiting for " + delayBeforeResumingMs + "ms before resuming the test application.");
return Q.delay(delayBeforeResumingMs);
})
.then<void>(() => {
.then<string>(() => {
// reopen the app
this.launchInstalledApplication(appId);
return this.launchInstalledApplication(appId);
});
}
/**
* Prepares the emulator for a test.
*/
prepareEmulatorForTest(appId: string): Q.Promise<string> {
return this.endRunningApplication(appId)
.then(() => { return ProjectManager.ProjectManager.execChildProcess("adb shell pm clear " + appId); });
}
}
/**

Просмотреть файл

@ -14,6 +14,7 @@ import fs = require("fs");
import mkdirp = require("mkdirp");
import platform = require("./platform");
import tu = require("./testUtil");
var del = require("del");
var archiver = require("archiver");
@ -22,7 +23,7 @@ var archiver = require("archiver");
* In charge of Cordova project related operations.
*/
export class ProjectManager {
public static ANDROID_KEY_PLACEHOLDER: string = "CODE_PUSH_ANDROID_DEPOYMENT_KEY";
public static ANDROID_KEY_PLACEHOLDER: string = "CODE_PUSH_ANDROID_DEPLOYMENT_KEY";
public static IOS_KEY_PLACEHOLDER: string = "CODE_PUSH_IOS_DEPLOYMENT_KEY";
public static SERVER_URL_PLACEHOLDER: string = "CODE_PUSH_SERVER_URL";
public static INDEX_JS_PLACEHOLDER: string = "CODE_PUSH_INDEX_JS_PATH";
@ -35,61 +36,82 @@ export class ProjectManager {
* Creates a new cordova test application at the specified path, and configures it
* with the given server URL, android and ios deployment keys.
*/
public static setupTemplate(destinationPath: string,
public static setupTemplate(projectDirectory: string,
templatePath: string,
serverURL: string,
androidKey: string,
iosKey: string,
appName: string,
appNamespace: string,
jsPath: string,
appVersion: string = ProjectManager.DEFAULT_APP_VERSION): Q.Promise<void> {
var configXmlPath = path.join(destinationPath, "config.xml");
var indexHtmlPath = path.join(destinationPath, "www/index.html");
var indexJsPath = path.join(destinationPath, "www/" + jsPath);
version: string = ProjectManager.DEFAULT_APP_VERSION): Q.Promise<string> {
var configXmlPath = path.join(projectDirectory, "config.xml");
var indexHtmlPath = path.join(projectDirectory, "www/index.html");
var setupScenario = "js/scenarioSetup.js";
var setupScenarioPath = path.join(projectDirectory, "www/", setupScenario);
if (fs.existsSync(destinationPath)) {
del.sync([destinationPath], { force: true });
if (fs.existsSync(projectDirectory)) {
del.sync([projectDirectory], { force: true });
}
mkdirp.sync(destinationPath);
mkdirp.sync(projectDirectory);
return ProjectManager.execAndLogChildProcess("cordova create " + destinationPath + " " + appNamespace + " " + appName + " --copy-from " + templatePath)
.then<void>(ProjectManager.replaceString.bind(undefined, configXmlPath, ProjectManager.ANDROID_KEY_PLACEHOLDER, androidKey))
.then<void>(ProjectManager.replaceString.bind(undefined, configXmlPath, ProjectManager.IOS_KEY_PLACEHOLDER, iosKey))
.then<void>(ProjectManager.replaceString.bind(undefined, configXmlPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<void>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<void>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.INDEX_JS_PLACEHOLDER, jsPath))
.then<void>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.CODE_PUSH_APP_VERSION_PLACEHOLDER, appVersion))
.then<void>(ProjectManager.replaceString.bind(undefined, indexJsPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL));
return ProjectManager.execChildProcess("cordova create " + projectDirectory + " " + appNamespace + " " + appName + " --copy-from " + templatePath)
// copy the correct values into the config.xml file
.then<string>(ProjectManager.replaceString.bind(undefined, configXmlPath, ProjectManager.ANDROID_KEY_PLACEHOLDER, androidKey))
.then<string>(ProjectManager.replaceString.bind(undefined, configXmlPath, ProjectManager.IOS_KEY_PLACEHOLDER, iosKey))
.then<string>(ProjectManager.replaceString.bind(undefined, configXmlPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
// copy the correct values into the index.html file
.then<string>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<string>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.CODE_PUSH_APP_VERSION_PLACEHOLDER, version))
.then<string>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<string>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.CODE_PUSH_APP_VERSION_PLACEHOLDER, version))
// use the setup scenario
.then<string>(ProjectManager.replaceString.bind(undefined, setupScenarioPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<string>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<string>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.INDEX_JS_PLACEHOLDER, setupScenario))
.then<string>(ProjectManager.replaceString.bind(undefined, indexHtmlPath, ProjectManager.CODE_PUSH_APP_VERSION_PLACEHOLDER, version));
}
/**
* Updates an already existing Cordova project.
* Sets up the scenario for a test in an already existing Cordova project.
*/
public static updateProject(destinationPath: string, templatePath: string, version: string, platform: string, jsPath: string, serverURL: string): Q.Promise<void> {
var templateIndexPath = path.join(templatePath, "www/index.html");
var destination = path.join(destinationPath, "www/index.html");
public static setupScenario(projectDirectory: string, templatePath: string, jsPath: string, serverURL: string, build: boolean = true, version: string = ProjectManager.DEFAULT_APP_VERSION): Q.Promise<string> {
var indexHtml = "www/index.html";
var templateIndexPath = path.join(templatePath, indexHtml);
var destinationIndexPath = path.join(projectDirectory, indexHtml);
var scenarioJs = "www/" + jsPath;
var templateScenarioJsPath = path.join(templatePath, scenarioJs);
var destinationScenarioJsPath = path.join(projectDirectory, scenarioJs);
return ProjectManager.copyFile(templateIndexPath, destinationPath, true)
.then<void>(ProjectManager.replaceString.bind(undefined, destination, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<void>(ProjectManager.replaceString.bind(undefined, destination, ProjectManager.INDEX_JS_PLACEHOLDER, jsPath))
.then<void>(ProjectManager.replaceString.bind(undefined, destination, ProjectManager.CODE_PUSH_APP_VERSION_PLACEHOLDER, version));
return ProjectManager.copyFile(templateIndexPath, destinationIndexPath, true)
.then<void>(ProjectManager.replaceString.bind(undefined, destinationIndexPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<void>(ProjectManager.replaceString.bind(undefined, destinationIndexPath, ProjectManager.INDEX_JS_PLACEHOLDER, jsPath))
.then<void>(ProjectManager.replaceString.bind(undefined, destinationIndexPath, ProjectManager.CODE_PUSH_APP_VERSION_PLACEHOLDER, version))
.then<void>(() => {
return ProjectManager.copyFile(templateScenarioJsPath, destinationScenarioJsPath, true);
})
.then<void>(ProjectManager.replaceString.bind(undefined, destinationScenarioJsPath, ProjectManager.SERVER_URL_PLACEHOLDER, serverURL))
.then<string>(() => {
return build ? ProjectManager.buildPlatform(projectDirectory) : ProjectManager.preparePlatform(projectDirectory);
});
}
/**
* Creates a CodePush update package zip for a Cordova project.
*/
public static createUpdateArchive(projectLocation: string, targetPlatform: platform.IPlatform, isDiff?: boolean): Q.Promise<string> {
public static createUpdateArchive(projectDirectory: string, isDiff?: boolean): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
var deferred = Q.defer<string>();
var archive = archiver.create("zip", {});
var archivePath = path.join(projectLocation, "update.zip");
var archivePath = path.join(projectDirectory, "update.zip");
console.log("Creating a project update archive at: " + archivePath);
if (fs.existsSync(archivePath)) {
fs.unlinkSync(archivePath);
}
var writeStream = fs.createWriteStream(archivePath);
var targetFolder = targetPlatform.getPlatformWwwPath(projectLocation);
var targetFolder = targetPlatform.getPlatformWwwPath(projectDirectory);
writeStream.on("close", function() {
deferred.resolve(archivePath);
@ -113,38 +135,69 @@ export class ProjectManager {
/**
* Adds a plugin to a Cordova project.
*/
public static addPlugin(projectFolder: string, plugin: string): Q.Promise<void> {
return ProjectManager.execAndLogChildProcess("cordova plugin add " + plugin, { cwd: projectFolder });
public static addPlugin(projectFolder: string, plugin: string): Q.Promise<string> {
return ProjectManager.execChildProcess("cordova plugin add " + plugin, { cwd: projectFolder });
}
/**
* Builds a specific platform of a Cordova project.
*/
public static buildPlatform(projectFolder: string, targetPlatform: platform.IPlatform): Q.Promise<void> {
return ProjectManager.execAndLogChildProcess("cordova build " + targetPlatform.getCordovaName(), { cwd: projectFolder });
public static buildPlatform(projectFolder: string): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
// don't log the iOS build output because it is too verbose and overflows the buffer
return ProjectManager.execChildProcess("cordova build " + targetPlatform.getCordovaName(), { cwd: projectFolder }, false);
}
/**
* Prepares a specific platform of a Cordova project.
*/
public static preparePlatform(projectFolder: string, targetPlatform: platform.IPlatform): Q.Promise<void> {
return ProjectManager.execAndLogChildProcess("cordova prepare " + targetPlatform.getCordovaName(), { cwd: projectFolder });
public static preparePlatform(projectFolder: string): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
return ProjectManager.execChildProcess("cordova prepare " + targetPlatform.getCordovaName(), { cwd: projectFolder });
}
/**
* Runs a project to a given target / platform.
* Launch the test app on the given target / platform.
*/
public static runPlatform(projectFolder: string, targetPlatform: platform.IPlatform, skipBuild: boolean = false, target?: string): Q.Promise<void> {
public static launchApplication(appNamespace: string): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
return targetPlatform.getEmulatorManager().launchInstalledApplication(appNamespace);
}
/**
* Kill the test app on the given target / platform.
*/
public static endRunningApplication(appNamespace: string): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
return targetPlatform.getEmulatorManager().endRunningApplication(appNamespace);
}
/**
* Prepares the emulator for a test.
*/
public static prepareEmulatorForTest(appNamespace: string): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
return targetPlatform.getEmulatorManager().prepareEmulatorForTest(appNamespace);
}
/**
* Runs the test app on the given target / platform.
*/
public static runPlatform(projectFolder: string, skipBuild: boolean = true, target?: string): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
var runTarget = target ? " --target " + target : "";
var nobuild = skipBuild ? " --nobuild" : "";
return ProjectManager.execAndLogChildProcess("cordova run " + targetPlatform.getCordovaName() + runTarget + nobuild, { cwd: projectFolder });
return ProjectManager.execChildProcess("cordova run " + targetPlatform.getCordovaName() + runTarget + nobuild, { cwd: projectFolder });
}
/**
* Adds a platform to a Cordova project.
*/
public static addPlatform(projectFolder: string, targetPlatform: platform.IPlatform, version?: string): Q.Promise<void> {
return ProjectManager.execAndLogChildProcess("cordova platform add " + targetPlatform.getCordovaName() + (version ? "@" + version : ""), { cwd: projectFolder });
public static addPlatform(projectFolder: string, version?: string): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
return ProjectManager.execChildProcess("cordova platform add " + targetPlatform.getCordovaName() + (version ? "@" + version : ""), { cwd: projectFolder });
}
/**
@ -157,34 +210,24 @@ export class ProjectManager {
/**
* Stops and restarts an application specified by its namespace identifier.
*/
public static restartApplication(targetPlatform: platform.IPlatform, namespace: string, testRunDirectory: string, targetEmulator: string): Q.Promise<void> {
var emulatorManager = targetPlatform.getEmulatorManager();
if (emulatorManager) {
return emulatorManager.restartApplication(namespace);
} else {
console.log("No emulator manager found!");
return null;
}
public static restartApplication(appNamespace: string): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
return targetPlatform.getEmulatorManager().restartApplication(appNamespace);
}
/**
* Navigates away from the application and then navigates back to it.
*/
public static resumeApplication(delayBeforeResumingMs: number, targetPlatform: platform.IPlatform, namespace: string, testRunDirectory: string, targetEmulator: string): Q.Promise<void> {
var emulatorManager = targetPlatform.getEmulatorManager();
if (emulatorManager) {
return emulatorManager.resumeApplication(namespace, delayBeforeResumingMs);
} else {
console.log("No emulator manager found!");
return null;
}
public static resumeApplication(namespace: string, delayBeforeResumingMs: number = 1000): Q.Promise<string> {
var targetPlatform = platform.PlatformResolver.resolvePlatform(tu.TestUtil.readTargetPlatform());
return targetPlatform.getEmulatorManager().resumeApplication(namespace, delayBeforeResumingMs);
}
/**
* Executes a child process and logs its output to the console.
* Executes a child process and logs its output to the console and returns its output in the promise as a string
*/
public static execAndLogChildProcess(command: string, options?: child_process.IExecOptions): Q.Promise<void> {
var deferred = Q.defer<void>();
public static execChildProcess(command: string, options?: child_process.IExecOptions, logOutput: boolean = true): Q.Promise<string> {
var deferred = Q.defer<string>();
options = options || {};
options.maxBuffer = 1024 * 500;
@ -192,14 +235,14 @@ export class ProjectManager {
console.log("Running command: " + command);
child_process.exec(command, options, (error: Error, stdout: Buffer, stderr: Buffer) => {
stdout && console.log(stdout);
if (logOutput) stdout && console.log(stdout);
stderr && console.error(stderr);
if (error) {
console.error(error);
deferred.reject(error);
} else {
deferred.resolve(undefined);
deferred.resolve(stdout.toString());
}
});

Просмотреть файл

@ -24,7 +24,7 @@
<platform name="android">
<allow-intent href="market:*" />
<!-- CodePush deployment key for the Android platform. -->
<preference name="CodePushDeploymentKey" value="CODE_PUSH_ANDROID_DEPOYMENT_KEY" />
<preference name="CodePushDeploymentKey" value="CODE_PUSH_ANDROID_DEPLOYMENT_KEY" />
</platform>
<platform name="ios">

Просмотреть файл

@ -37,12 +37,12 @@ var app = {
function () {
/* success */
app.sendTestMessage("RESTART_SUCCEEDED");
callback();
callback && callback();
},
function () {
/* error */
app.sendTestMessage("RESTART_FAILED");
callback();
callback && callback();
});
},
/* sends the current and pending package to the mock server */
@ -53,7 +53,7 @@ var app = {
window.codePush.getCurrentPackage(function (currentPackage) {
console.log("Current package: " + currentPackage);
app.sendTestMessage("CURRENT_PACKAGE", [currentPackage ? currentPackage.packageHash : null]);
callback();
callback && callback();
});
});
},

Просмотреть файл

@ -0,0 +1,19 @@
var app = {
// Application Constructor
initialize: function () {
this.bindEvents();
},
bindEvents: function () {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function () {
app.receivedDeviceReady();
},
// Update DOM on a Received Event
receivedDeviceReady: function () {
document.getElementById("deviceready").innerText = "Device is ready (scenario - setup)";
console.log('Received Event: deviceready');
}
};
app.initialize();

Просмотреть файл

@ -0,0 +1,45 @@
var app = {
// Application Constructor
initialize: function () {
this.bindEvents();
},
bindEvents: function () {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function () {
app.receivedDeviceReady();
},
// Update DOM on a Received Event
receivedDeviceReady: function () {
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync mandatory)";
console.log('Received Event: deviceready');
/* invoke sync with no UI options */
window.codePush.sync(
function (status) {
switch(status) {
case SyncStatus.UP_TO_DATE:
case SyncStatus.UPDATE_INSTALLED:
case SyncStatus.UPDATE_IGNORED:
case SyncStatus.ERROR:
case SyncStatus.IN_PROGRESS:
app.sendTestMessage("SYNC_STATUS", [status]);
default:
break;
}
},
{
installMode: InstallMode.ON_NEXT_RESTART
});
},
sendTestMessage: function (message, args) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "CODE_PUSH_SERVER_URL/reportTestMessage", false);
var body = JSON.stringify({ message: message, args: args });
console.log("Sending test message body: " + body);
xhr.setRequestHeader("Content-type", "application/json");
xhr.send(body);
}
};
app.initialize();

Просмотреть файл

@ -0,0 +1,46 @@
var app = {
// Application Constructor
initialize: function () {
this.bindEvents();
},
bindEvents: function () {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function () {
app.receivedDeviceReady();
},
// Update DOM on a Received Event
receivedDeviceReady: function () {
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync mandatory)";
console.log('Received Event: deviceready');
/* invoke sync with no UI options */
window.codePush.sync(
function (status) {
switch(status) {
case SyncStatus.UP_TO_DATE:
case SyncStatus.UPDATE_INSTALLED:
case SyncStatus.UPDATE_IGNORED:
case SyncStatus.ERROR:
case SyncStatus.IN_PROGRESS:
app.sendTestMessage("SYNC_STATUS", [status]);
default:
break;
}
},
{
installMode: InstallMode.IMMEDIATE,
mandatoryInstallMode: InstallMode.ON_NEXT_RESTART
});
},
sendTestMessage: function (message, args) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "CODE_PUSH_SERVER_URL/reportTestMessage", false);
var body = JSON.stringify({ message: message, args: args });
console.log("Sending test message body: " + body);
xhr.setRequestHeader("Content-type", "application/json");
xhr.send(body);
}
};
app.initialize();

Просмотреть файл

@ -0,0 +1,50 @@
var app = {
// Application Constructor
initialize: function () {
this.bindEvents();
},
bindEvents: function () {
document.addEventListener('deviceready', this.onDeviceReady, false);
document.addEventListener('resume', this.onResume, false);
},
onDeviceReady: function () {
app.receivedDeviceReady();
},
onResume: function () {
app.sendTestMessage("APPLICATION_RESUMED");
},
// Update DOM on a Received Event
receivedDeviceReady: function () {
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync mandatory)";
console.log('Received Event: deviceready');
/* invoke sync with no UI options */
window.codePush.sync(
function (status) {
switch(status) {
case SyncStatus.UP_TO_DATE:
case SyncStatus.UPDATE_INSTALLED:
case SyncStatus.UPDATE_IGNORED:
case SyncStatus.ERROR:
case SyncStatus.IN_PROGRESS:
app.sendTestMessage("SYNC_STATUS", [status]);
default:
break;
}
},
{
installMode: InstallMode.ON_NEXT_RESTART,
mandatoryInstallMode: InstallMode.ON_NEXT_RESUME
});
},
sendTestMessage: function (message, args) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "CODE_PUSH_SERVER_URL/reportTestMessage", false);
var body = JSON.stringify({ message: message, args: args });
console.log("Sending test message body: " + body);
xhr.setRequestHeader("Content-type", "application/json");
xhr.send(body);
}
};
app.initialize();

Просмотреть файл

@ -15,7 +15,7 @@ var app = {
},
// Update DOM on a Received Event
receivedDeviceReady: function () {
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync on restart minbackdur = 30s)";
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync on restart with minbackdur)";
console.log('Received Event: deviceready');
/* invoke sync with no UI options */
window.codePush.sync(

Просмотреть файл

@ -15,7 +15,7 @@ var app = {
},
// Update DOM on a Received Event
receivedDeviceReady: function () {
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync on resume minbackdur = 0s)";
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync on resume with no minbackdur)";
console.log('Received Event: deviceready');
/* invoke sync with no UI options */
window.codePush.sync(

Просмотреть файл

@ -15,7 +15,7 @@ var app = {
},
// Update DOM on a Received Event
receivedDeviceReady: function () {
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync on resume minbackdur = 30s)";
document.getElementById("deviceready").innerText = "Device is ready (scenario - sync on resume with minbackdur)";
console.log('Received Event: deviceready');
/* invoke sync with no UI options */
window.codePush.sync(
@ -34,7 +34,7 @@ var app = {
},
{
installMode: InstallMode.ON_NEXT_RESUME,
minimumBackgroundDuration: 15
minimumBackgroundDuration: 10
});
},
sendTestMessage: function (message, args) {

Просмотреть файл

@ -11,7 +11,7 @@ var app = {
},
// Update DOM on a Received Event
receivedDeviceReady: function () {
document.getElementById("deviceready").innerText = "Device is ready (scenario - update sync)";
document.getElementById("deviceready").innerText = "Device is ready (scenario - update sync 2x)";
console.log('Received Event: deviceready');
app.sendTestMessage("DEVICE_READY_AFTER_UPDATE");
/* invoke sync with UI options such that the update will not be installed */

Разница между файлами не показана из-за своего большого размера Загрузить разницу

7
typings/codePush.d.ts поставляемый
Просмотреть файл

@ -299,7 +299,7 @@ declare enum InstallMode {
*/
interface InstallOptions {
/**
* Used to specity the InstallMode used for the install operation. This is optional and defaults to InstallMode.ON_NEXT_RESTART.
* Used to specify the InstallMode used for the install operation. This is optional and defaults to InstallMode.ON_NEXT_RESTART.
*/
installMode?: InstallMode;
@ -307,6 +307,11 @@ interface InstallOptions {
* If installMode === ON_NEXT_RESUME, the minimum amount of time (in seconds) which needs to pass with the app in the background before an update install occurs when the app is resumed.
*/
minimumBackgroundDuration?: number;
/**
* Used to specify the InstallMode used for the install operation if the update is mandatory. This is optional and defaults to InstallMode.IMMEDIATE.
*/
mandatoryInstallMode?: InstallMode;
}
/**

Просмотреть файл

@ -386,6 +386,7 @@ class CodePush implements CodePushCordovaPlugin {
ignoreFailedUpdates: true,
installMode: InstallMode.ON_NEXT_RESTART,
minimumBackgroundDuration: 0,
mandatoryInstallMode: InstallMode.IMMEDIATE,
updateDialog: false,
deploymentKey: undefined
};

Просмотреть файл

@ -110,20 +110,23 @@ class LocalPackage extends Package implements ILocalPackage {
LocalPackage.getCurrentOrDefaultPackage((oldPackage: LocalPackage) => {
LocalPackage.backupPackageInformationFile((backupError: Error) => {
backupError && CodePushUtil.logMessage("First update: back up package information skipped. ");
/* continue on error, current package information is missing if this is the fist update */
/* continue on error, current package information is missing if this is the first update */
this.writeNewPackageMetadata(deployDir, (writeMetadataError: Error) => {
if (writeMetadataError) {
installError && installError(writeMetadataError);
} else {
var invokeSuccessAndInstall = () => {
CodePushUtil.logMessage("Install succeeded.");
if (installOptions.installMode === InstallMode.IMMEDIATE) {
var installModeToUse: InstallMode = this.isMandatory ? installOptions.mandatoryInstallMode : installOptions.installMode;
if (installModeToUse === InstallMode.IMMEDIATE) {
/* invoke success before navigating */
installSuccess && installSuccess();
/* no neeed for callbacks, the javascript context will reload */
cordova.exec(() => { }, () => { }, "CodePush", "install", [deployDir.fullPath, installOptions.installMode.toString(), installOptions.minimumBackgroundDuration.toString()]);
/* no need for callbacks, the javascript context will reload */
cordova.exec(() => { }, () => { }, "CodePush", "install", [deployDir.fullPath,
installModeToUse.toString(), installOptions.minimumBackgroundDuration.toString()]);
} else {
cordova.exec(() => { installSuccess && installSuccess(); }, () => { installError && installError(); }, "CodePush", "install", [deployDir.fullPath, installOptions.installMode.toString(), installOptions.minimumBackgroundDuration.toString()]);
cordova.exec(() => { installSuccess && installSuccess(); }, () => { installError && installError(); }, "CodePush", "install", [deployDir.fullPath,
installModeToUse.toString(), installOptions.minimumBackgroundDuration.toString()]);
}
};
@ -444,7 +447,8 @@ class LocalPackage extends Package implements ILocalPackage {
if (!LocalPackage.DefaultInstallOptions) {
LocalPackage.DefaultInstallOptions = {
installMode: InstallMode.ON_NEXT_RESTART,
minimumBackgroundDuration: 0
minimumBackgroundDuration: 0,
mandatoryInstallMode: InstallMode.IMMEDIATE
};
}