telerik / mobile-cli-lib

Contains common infrastructure for CLIs - mainly AppBuilder, NativeScript, DDB and Proton.
Apache License 2.0
11 stars 10 forks source link

mobile-cli-lib

Provides an easy way for working with devices. Contains common infrastructure for CLIs - mainly AppBuilder and NativeScript.

Installation

Latest version: 0.22.0

Release date: 2016, December 15

System Requirements

Before installing the mobile-cli-lib, verify that your system meets the following requirements.

Windows Systems

Minimum Software Requirements

Additional Software Requirements for iOS On-Device Deployment

The bitness of Node.js and iTunes must match.

Additional Software Requirements for Android On-Device Deployment

Additional Software Requirements for Windows Phone On-Device Deployment

In this version of the mobile-cli-lib, you cannot deploy and LiveSync to connected Windows Phone devices from the command line.

OS X Systems

Minimum Software Requirements

Additional Software Requirements for iOS On-Device Deployment

Additional Software Requirements for Android On-Device Deployment

Additional Software Requirements for Windows Phone On-Device Deployment

In this version of the mobile-cli-lib, you cannot deploy and LiveSync to connected Windows Phone devices from the command line.

Linux Systems

Minimum Software Requirements

Additional Software Requirements for iOS On-Device Deployment

In this version of the mobile-cli-lib, you cannot deploy and LiveSync on connected iOS devices from the command line. You need to manually deploy the application package using iTunes.

Additional Software Requirements for Android On-Device Deployment

Additional Software Requirements for Windows Phone On-Device Deployment

In this version of the mobile-cli-lib, you cannot deploy and LiveSync to connected Windows Phone devices from the command line.

Install the mobile-cli-lib

The mobile-cli-lib should be added as dependency in your project's package.json.

Usage

In order to use mobile-cli-lib, just add a reference to it in your package.json:

dependencies: {
    "mobile-cli-lib": "0.4.0"
}

After that execute npm install in the directory, where your package.json is located. This command will install all your dependencies in node_modules directory. Now you are ready to use mobile-cli-lib in your project:

var common = require("mobile-cli-lib");
common.fs.getFileSize("D:\\Work\\t.txt")
    .then(function (result) {
        console.log("File size is: " + result);
        return result;
    }, function (err) {
        console.log("Error happened:");
        console.log(err);
    });

Sample application

You can find a sample application here. Just download the zip file and execute npm install in the project directory. After that you can execute node index.js in your terminal. In case you have file D:\Work\t.txt, the application will show you its size. In case you do not have such file, the application will show an error. You can change the filename in index.js.

Public API

This section contains information about each public method.

Device related public API, exposes IDeviceInfo data, that contains the following information:

/**
 * Describes available information for a device.
 */
interface IDeviceInfo {
    /**
     * Unique identifier of the device.
     */
    identifier: string;

    /**
     * The name of the device.
     * For Android this is the value of device's 'ro.product.name' property.
     * For iOS this is the value of device's 'DeviceName' property.
     */
    displayName: string;

    /**
     * Device model.
     * For Android this is the value of device's 'ro.product.model' property.
     * For iOS this is the value of device's 'ProductType' property.
     */
    model: string;

    /**
     * Version of the OS.
     * For Android this is the value of device's 'ro.build.version.release' property.
     * For iOS this is the value of device's 'ProductVersion' property.
     */
    version: string;

    /**
     * Vendor of the device.
     * For Android this is the value of device's 'ro.product.brand' property.
     * For iOS the value is always "Apple".
     */
    vendor: string;

    /**
     * Device's platform.
     * Can be Android or iOS.
     */
    platform: string;

    /**
     * Status of device describing if you can work with this device or there's communication error.
     * Can be Connected or Unreachable.
     */
    status: string;

    /**
     * Additional information for errors that prevents working with this device.
     * It will be null when status is Connected.
     */
    errorHelp: string;

    /**
     * Defines if the device is tablet or not.
     * For Android the value will be true when device's 'ro.build.characteristics' property contains "tablet" word or when the 'ro.build.version.release' is 3.x
     * For iOS the value will be true when device's 'ProductType' property contains "ipad" word.
     */
    isTablet: boolean;

    /**
     * Optional property describing the color of the device.
     * Available for iOS only - the value of device's 'DeviceColor' property.
     */
    color?: string;

    /**
     *  Optional property describing the architecture of the device
     *  Available for iOS only - can be "armv7" or "arm64"
     */
    activeArchitecture?: string;
}

Module companionAppsService

Stability 2 - Stable

companionAppsService gives access to companion apps identifiers.

Module deviceEmitter

Stability 2 - Stable

deviceEmitter module is used to emit different events related to devices attached to the system. You can use deviceEmitter to add handles for the following events:

Sample usage:

require("mobile-cli-lib")
    .deviceEmitter.on("debuggableViewFound",  function(deviceIdentifier, appIdentifier, debuggableViewInfo) {
    console.log("On device " + deviceIdentifier + " the application " + appIdentifier  + " now has new WebView: " + debuggableViewInfo);
});

Sample result for debuggableViewInfo will be:

{
    "description": "",
    "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4050",
    "id": "4050",
    "title": "New tab",
    "type": "page",
    "url": "chrome-native://newtab/",
    "webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4050"
}

Sample usage:

require("mobile-cli-lib")
    .deviceEmitter.on("debuggableViewLost",  function(deviceIdentifier, appIdentifier, debuggableViewInfo) {
    console.log("On device " + deviceIdentifier + " the application " + appIdentifier  + " now cannot debug WebView: " + debuggableViewInfo);
});

Sample result for debuggableViewInfo will be:

{
    "description": "",
    "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4050",
    "id": "4050",
    "title": "New tab",
    "type": "page",
    "url": "chrome-native://newtab/",
    "webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4050"
}

Sample usage:

require("mobile-cli-lib")
    .deviceEmitter.on("debuggableViewChanged",  function(deviceIdentifier, appIdentifier, debuggableViewInfo) {
    console.log("On device " + deviceIdentifier + " the application " + appIdentifier  + " has changes in WebView: " + debuggableViewInfo);
});

Sample result for debuggableViewInfo will be:

{
    "description": "",
    "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4050",
    "id": "4050",
    "title": "New tab 2",
    "type": "page",
    "url": "chrome-native://newtab/",
    "webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4050"
}

Module devicesService

Stability: 2 - Stable

This module allows interaction with devices. You can get a list of the attached devices or deploy on specific devices.

Sample usage:

Promise.all(require("mobile-cli-lib")
                .devicesService
                .deployOnDevices(["129604ab96a4d0053023b4bf5b288cf34a9ed5fa", "153544fa45f4a5646543b5bf1b221fe31a8fa6bc"], "./app.ipa", "com.telerik.testApp", "Test App"))
            .then(function(data) {
                console.log(data);
            }, function(err) {
                console.log(err);
            });

NOTE: This method will try to start the application on each device in order to understand is LiveSync supported. Sample usage:

Promise.all(require("mobile-cli-lib")
.devicesService
.isAppInstalledOnDevices(devicesFound, "com.telerik.myApp", "cordova"))
.then(function(data) {
console.log(data);
}, function(err) {
console.log(err);
});

Sample result will be:

[{
"deviceIdentifier": "deviceId1",
"appIdentifier": "appId",
"isInstalled": true,
"isLiveSyncSupported": true
}, {
"deviceIdentifier": "deviceId2",
"appIdentifier": "appId",
"isInstalled": false,
"isLiveSyncSupported": false
}]

Sample usage:

require("mobile-cli-lib").devicesService.mapAbstractToTcpPort("4df18f307d8a8f1b", "com.telerik.test", "Cordova")
    .then(function(port) {
        console.log(port);
    }, function(err) {
        console.log(err);
    });

Sample usage:

Promise.all(require("mobile-cli-lib").devicesService.getDebuggableApps(["4df18f307d8a8f1b", "JJY5KBTW75TCHQUK"]))
    .then(function(data) {
        data.forEach(function(apps) {
            console.log(apps);
        });
    }, function(err) {
        console.log(err);
    });

Sample result will be:

[[{
    "deviceIdentifier": "4df18f307d8a8f1b",
    "appIdentifier": "com.telerik.Fitness",
    "framework": "NativeScript"
}, {
    "deviceIdentifier": "4df18f307d8a8f1b",
    "appIdentifier": "com.telerik.livesynctest",
    "framework": "Cordova"
}], [{
    "deviceIdentifier": "JJY5KBTW75TCHQUK",
    "appIdentifier": "com.telerik.PhotoAlbum",
    "framework": "NativeScript"
}]]

Sample usage:

Promise.all(require("mobile-cli-lib").devicesService.getDebuggableApps(["4df18f307d8a8f1b", "JJY5KBTW75TCHQUK"]))
    .then(function(data) {
        data.forEach(function(apps) {
            console.log(apps);
        });
    }, function(err) {
        console.log(err);
    });

Sample result will be:

[[{
    "deviceIdentifier": "4df18f307d8a8f1b",
    "appIdentifier": "com.telerik.Fitness",
    "framework": "NativeScript"
}, {
    "deviceIdentifier": "4df18f307d8a8f1b",
    "appIdentifier": "com.telerik.livesynctest",
    "framework": "Cordova"
}], [{
    "deviceIdentifier": "JJY5KBTW75TCHQUK",
    "appIdentifier": "com.telerik.PhotoAlbum",
    "framework": "NativeScript"
}]]
/**
 * Describes information for WebView that can be debugged.
 */
interface IDebugWebViewInfo {
    /**
     * Short description of the view.
     */
    description: string;

    /**
     * Url to the devtools.
     * @example http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4024
     */
    devtoolsFrontendUrl: string;

    /**
     * Unique identifier of the web view. Could be number or GUID.
     * @example 4027
     */
    id: string;

    /**
     * Title of the WebView.
     * @example https://bit.ly/12345V is not available
     */
    title: string;

    /**
     * Type of the WebView.
     * @example page
     */
    type: string;

    /**
     * URL loaded in the view.
     * @example https://bit.ly/12345V
     */
    url: string;

    /**
     * Debugger URL.
     * @example ws://127.0.0.1:53213/devtools/page/4027
     */
    webSocketDebuggerUrl: string;
}

Sample usage:

require("mobile-cli-lib")
    .devicesService
    .getDebuggableViews("4df18f307d8a8f1b", "com.telerik.cordovaApp")
    .then(function(data) {
        console.log(data);
    }, function(err) {
        console.log(err);
    });

Sample result will be:

[{
        "description": "",
        "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4050",
        "id": "4050",
        "title": "New tab",
        "type": "page",
        "url": "chrome-native://newtab/",
        "webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4050"
    },

    {
        "description": "",
        "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@211d45a5b74b06d12bb016f3c4d54095faf2646f/inspector.html?ws=127.0.0.1:53213/devtools/page/4032",
        "id": "4032",
        "title": "New tab",
        "type": "page",
        "url": "chrome-native://newtab/",
        "webSocketDebuggerUrl": "ws://127.0.0.1:53213/devtools/page/4032"
    }
]

Sample usage:

require("mobile-cli-lib").devicesService.getInstalledApplications("4df18f307d8a8f1b")
    .then(function(appIdentifiers) {
        console.log("Installed applications are: ", appIdentifiers);
    }, function(err) {
        console.log(err);
    });

Sample result of the method call is:

["com.telerik.app1", "org.nativescript.app1"]

Module liveSyncService

Stability: 1 - Could be changed due to some new requirments.

This module allows LiveSync applications on different devices.

The following types are used:

/**
 * Describes the result of a single livesync operation started by Proton.
 */
interface ILiveSyncOperationResult {
    /**
     * Defines if the operation is successful (set to true) or not (value is false).
     */
    isResolved: boolean;

    /**
     * Error when livesync operation fails. If `isResolved` is true, error will be undefined.
     */
    error?: Error;
}

/**
 * Describes result of all LiveSync operations per device.
 */
interface IDeviceLiveSyncResult {
    /**
     * Identifier of the device.
     */
    deviceIdentifier: string;

    /**
     * Result of LiveSync operation for application.
     */
    liveSyncToApp?: ILiveSyncOperationResult;

    /**
     * Result of LiveSync operation to companion app.
     */
    liveSyncToCompanion?: ILiveSyncOperationResult;
}

Sample usage:

var deviceInfos = [{"deviceIdentifier": "129604ab96a4d0053023b4bf5b288cf34a9ed5fa", "syncToApp": true, "syncToCompanion": false},
                    {"deviceIdentifier": "153544fa45f4a5646543b5bf1b221fe31a8fa6bc", "syncToApp": true, "syncToCompanion": false}];
// Full Sync - the whole project dir will be synced
Promise.all(require("mobile-cli-lib").liveSyncService.livesync(deviceInfos, projectDir))
    .then(function(result) {
            console.log("Finished with full sync, result is: ", result);
    }).catch(function(err) {
            console.log("Error while livesyncing: ", err);
    });

// Or use livesync only for some files:
var filesToSync = [path.join(projectDir,"app","components", "homeView", "homeView.xml"),
                    path.join(projectDir,"app","components", "addressView", "addressView.xml")]
Promise.all(require("mobile-cli-lib").liveSyncService.livesync(deviceInfos, projectDir, filesToSync))
    .then(function(result) {
            console.log("Finished with partial sync, result is: ", result);
    }).catch(function(err) {
            console.log("Error while livesyncing: ", err);
    });

Sample usage:

var deviceInfos = [{"deviceIdentifier": "129604ab96a4d0053023b4bf5b288cf34a9ed5fa", "syncToApp": true, "syncToCompanion": false},
                    {"deviceIdentifier": "153544fa45f4a5646543b5bf1b221fe31a8fa6bc", "syncToApp": true, "syncToCompanion": false}];

var filesToSync = [path.join(projectDir,"app","components", "homeView", "homeView.xml"),
                    path.join(projectDir,"app","components", "addressView", "addressView.xml")]
Promise.all(require("mobile-cli-lib").liveSyncService.deleteFiles(deviceInfos, projectDir, filesToSync))
    .then(function(result) {
            console.log("Finished with deleting files, result is: ", result);
    }).catch(function(err) {
            console.log("Error while deleting files: ", err);
    });

Module npmService

Stability: 1 - Could be changed due to some new requirments.

This module is used to install or uninstall packages from npm.

The following types are used:

/**
 * Describes information for single npm dependency that has to be installed.
 */
interface INpmDependency {
    /**
     * Name of the dependency.
     */
    name: string;

    /**
     * @optional The version of the dependency that has to be installed.
     */
    version?: string;

    /**
     * Defines if @types/<name> should be installed as well.
     */
    installTypes: boolean;
}

/**
 * Describes the result of npm install command.
 */
interface INpmInstallResult {
    /**
     * The result of installing a single dependency.
     */
    result?: INpmInstallDependencyResult,

    /**
     * The error that occurred during the operation.
     */
    error?: Error;
}

Note that the last argument - config can be used to pass flags to the npm install opration like save, save-dev, ignore-scripts and so on.

Sample usage:

// Install all dependencies from package.json.
require("mobile-cli-lib").npmService.install("lodash@3.x", "D:\\test\\project", { save: true, "save-exact": true })
    .then(function(result) {
            console.log("The npm result is: ", result);
    }).catch(function(err) {
            console.log("Error while installing packages from npm: ", err);
    });

Sample result will be:

{}
// Install specific dependency from npm.
var dependency = {
    name: "lodash",
    version: "4.15.0",
    installTypes: true
};

require("mobile-cli-lib").npmService.install("D:\\test\\project", dependency)
    .then(function(result) {
            console.log("The npm result is: ", result);
    }).catch(function(err) {
            console.log("Error while installing packages from npm: ", err);
    });

Sample result will be:

{
    "result": {
        "isInstalled": true,
        "isTypesInstalled": true
    }
}

Sample usage:

require("mobile-cli-lib").npmService.uninstall("D:\\test\\project", "lodash")
    .then(function() {
            console.log("The dependency is uninstalled.");
    }).catch(function(err) {
            console.log("Error while uninstalling packages from npm: ", err);
    });

Module typeScriptService

Stability: 1 - Could be changed due to some new requirments.

This module is used to transpile TypeScript files.

The following types are used:

interface ITypeScriptCompilerOptions {
    /**
     * Specify the codepage to use when opening source files.
     */
    codePage?: number;

    /**
     * Generates corresponding .d.ts file.
     */
    declaration?: boolean;

    /**
     * Specifies the location where debugger should locate map files instead of generated locations.
     */
    mapRoot?: string;

    /**
     * Specify module code generation: 'commonjs' or 'amd'.
     */
    module?: string;

    /**
     * Warn on expressions and declarations with an implied 'any' type.
     */
    noImplicitAny?: boolean;

    /**
     * Concatenate and emit output to single file.
     */
    outFile?: string;

    /**
     * Redirect output structure to the directory.
     */
    outDir?: string;

    /**
     * Do not emit comments to output.
     */
    removeComments?: boolean;

    /**
     * Generates corresponding .map file.
     */
    sourceMap?: boolean;

    /**
     * Specifies the location where debugger should locate TypeScript files instead of source locations.
     */
    sourceRoot?: string;

    /**
     * Specify ECMAScript target version: 'ES3' (default), or 'ES5'.
     */
    target?: string;

    /**
     * Do not emit outputs if any errors were reported.
     */
    noEmitOnError?: boolean;

    [key: string]: any;
}

/**
 * Describes the options for transpiling TypeScript files.
 */
interface ITypeScriptTranspileOptions {
    /**
     * Describes the options in tsconfig.json file.
     */
    compilerOptions?: ITypeScriptCompilerOptions;

    /**
     * The default options which will be used if there is no tsconfig.json file.
     */
    defaultCompilerOptions?: ITypeScriptCompilerOptions;

    /**
     * Path to the default .d.ts files.
     */
    pathToDefaultDefinitionFiles?: string;
}

Sample usage:

// Transpile only 2 files.
var projectDir = "D:\\test\\project";
var filesToTranspile = [path.join(projectDir,"app","components", "homeView", "homeView.ts"),
                    path.join(projectDir,"app","components", "addressView", "addressView.ts")];

require("mobile-cli-lib").typeScriptService.transpile(projectDir, filesToTranspile)
    .then(function(result) {
            console.log("TypeScript compiler result: ", result);
    }).catch(function(err) {
            console.log("Error while transpiling files: ", err);
    });

Sample result if there are no errors will be:

""

Sample result with errors will be:

"app/components/homeView/homeView.ts(19,1): error TS2304: Cannot find name 'a'.
app/components/homeView/homeView.ts(20,1): error TS2304: Cannot find name 'b'."
// Transpile all files in project.
require("mobile-cli-lib").typeScriptService.transpile("D:\\test\\project")
    .then(function(result) {
            console.log("TypeScript compiler result: ", result);
    }).catch(function(err) {
            console.log("Error while transpiling files: ", err);
    });

Technical details

Injector

Similar to AngularJS, mobile-cli-lib is using $injector to retrive object instances, instantiate types and load modules. Each module must be registered in the $injector, so when another module depends on it, the $injector will create a new instance of the dependency or reuse already created one.

How to add new module

class DeviceService {
}

NOTE: The reference path at the top must point the the root of the project, where .d.ts file is created by grunt.

NOTE: In case you do not place access modifier (private, protected or public, you'll be able to use the dependant module only in the constructor.

NOTE: The name of the module must be exactly the same as the one used for registering in the $injector, in this case this is fs module. The preceding dollar symbol $ is mandatory. Now you can access fs methods by using this.$fs.<method>.

How to make a method public

In order to expose public API, we use TypeScript decorators and some "magic" in our bootstrapping. When you want to expose method B from class A, you have to do the following:

$injector.requirePublic("deviceService", "./device-service")

IMPORTANT: exported decorator requires one parameter which MUST be the first parameter passed to requirePublic method. This is the name of the module that will be publicly exposed.

After you have executed these two steps, you can start using your publicly available method:

var common = require("mobile-cli-lib");
common.deviceService.listDevices() /* NOTE: here we are not using the class name DeviceService, but the module name - deviceService */
    .then(function (a) {
        console.log("After promise had returned.");
        return a;
    })
    .catch(function (err) {
        console.log("Error happened:");
        console.log(err);
    });

Behind the scenes of generating public API

requirePublic method of the injector is doing some "magic" in order to support lazy loading, correct dependency resolving and exposing only some of the methods, not the whole power of the common lib. When you require mobile-cli-lib module, you receive $injector's publicApi - it is the "exported one". requirePublic method defines getter for each module that is passed, for example when you say:

    $injector.requirePublic("deviceService", "./device-service")

a new property is added to publicApi - deviceService and a getter is added for it. When you try to access this module, require("mobile-cli-lib").deviceService.listDevices(), the getter is called. It resolves the module, by parsing the provided file (./device-service) and that's the time when decorators are executed. For each decorated method, a new entry in $injector.publicApi.__modules__ is created. This is not the same method that you've decorated - it's entirely new method, that returns a Promise. The new method will be used in the publicApi, while original implementation will still be used in all other places in the code. The promisified method will call the original one (in a separate Fiber) and will resolve the Promise with the result of the method.

Issues

Missing dependencies

Some of our modules must be added: staticConfig, config, analyticsService, etc.

Tests for injector

Add more tests for yok and for register decorator.