├── definitions ├── watchr.d.ts ├── iconv-lite.d.ts ├── osenv.d.ts ├── byline.d.ts ├── open.d.ts ├── plist.d.ts ├── validator.d.ts ├── simple-plist.d.ts ├── user-settings.d.ts ├── rimraf.d.ts ├── temp.d.ts ├── cli-global.d.ts ├── node-uuid-cjs.d.ts ├── colors.d.ts ├── logger.d.ts ├── eqatec.d.ts ├── xmlhttprequest.d.ts ├── commands-service.d.ts ├── eqatec-analytics.d.ts ├── minimatch.d.ts ├── log4js.d.ts ├── config.d.ts ├── node-uuid.d.ts ├── node-uuid-base.d.ts ├── yok.d.ts └── commands.d.ts ├── bin └── common-lib.js ├── common-lib.ts ├── docs ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.ttf │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.ttf │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.ttf │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.ttf │ ├── OpenSans-Regular-webfont.woff │ ├── OpenSans-Semibold-webfont.eot │ ├── OpenSans-Semibold-webfont.ttf │ ├── OpenSans-Semibold-webfont.woff │ ├── OpenSans-SemiboldItalic-webfont.eot │ ├── OpenSans-SemiboldItalic-webfont.ttf │ └── OpenSans-SemiboldItalic-webfont.woff └── helpers │ ├── basic-page.html │ └── basic-extensions-page.html ├── test ├── mocha.opts ├── resources │ └── sampleZipFileTest.zip ├── unit-tests │ ├── mocks │ │ ├── public-api-mocks.ts │ │ ├── decorators-cache.ts │ │ └── decorators-invoke-before.ts │ ├── mobile │ │ └── genymotion │ │ │ └── virtualbox-service.ts │ └── process-service.ts ├── test-bootstrap.ts └── definitions │ └── mocha.d.ts ├── resources ├── platform-tools │ ├── android │ │ ├── linux │ │ │ └── adb │ │ ├── darwin │ │ │ └── adb │ │ └── win32 │ │ │ ├── adb.exe │ │ │ ├── AdbWinApi.dll │ │ │ ├── fastboot.exe │ │ │ └── AdbWinUsbApi.dll │ └── unzip │ │ └── win32 │ │ └── unzip.exe └── messages │ └── errorMessages.json ├── vendor └── License.txt ├── mobile ├── logging-levels.ts ├── device-platforms-constants.ts ├── device-app-data │ ├── device-app-data-base.ts │ └── device-app-data-factory.ts ├── ios │ ├── simulator │ │ ├── ios-sim-resolver.ts │ │ ├── ios-simulator-file-system.ts │ │ ├── ios-simulator-log-provider.ts │ │ └── ios-simulator-device.ts │ ├── ios-log-filter.ts │ └── ios-device-product-name-mapper.ts ├── device-log-provider.ts ├── mobile-core │ ├── device-discovery.ts │ ├── ios-device-discovery.ts │ ├── android-emulator-discovery.ts │ └── android-device-discovery.ts ├── android │ ├── device-android-debug-bridge.ts │ ├── android-livesync-service.ts │ ├── android-log-filter.ts │ └── android-ini-file-parser.ts ├── local-to-device-path-data-factory.ts ├── log-filter.ts ├── wp8 │ └── wp8-emulator-services.ts ├── device-log-provider-base.ts ├── emulator-helper.ts └── mobile-helper.ts ├── .npmignore ├── doctor.d.ts ├── .vscode └── settings.json ├── opener.ts ├── .travis.yml ├── services ├── analytics │ └── google-analytics-custom-dimensions.d.ts ├── qr.ts ├── plugins │ ├── npm-plugins-source.ts │ ├── plugins-source-base.ts │ ├── npm-registry-plugins-source.ts │ ├── npm-plugins-service.ts │ └── print-plugins-service.ts ├── micro-templating-service.ts ├── settings-service.ts ├── ios-notification-service.ts ├── process-service.ts ├── dynamic-help-service.ts ├── livesync │ └── sync-batch.ts ├── xcode-select-service.ts ├── proxy-service.ts ├── lockfile.ts ├── project-files-provider-base.ts ├── cancellation.ts ├── messages-service.ts └── message-contract-generator.ts ├── messages ├── messages.interface.d.ts └── messages.ts ├── validators ├── validation-result.ts ├── iTunes-validator.ts └── project-name-validator.ts ├── scripts └── node-args.js ├── tsconfig.json ├── os-info.ts ├── commands ├── doctor.ts ├── preuninstall.ts ├── proxy │ ├── proxy-get.ts │ ├── proxy-base.ts │ └── proxy-clear.ts ├── device │ ├── uninstall-application.ts │ ├── stop-application.ts │ ├── list-applications.ts │ ├── put-file.ts │ ├── get-file.ts │ ├── run-application.ts │ ├── list-files.ts │ └── device-log-stream.ts ├── help.ts ├── generate-messages.ts ├── post-install.ts ├── analytics.ts └── autocompletion.ts ├── config-base.ts ├── .gitignore ├── test-scripts ├── mocha.js └── istanbul.js ├── plist-parser.ts ├── appbuilder ├── mobile │ ├── appbuilder-companion-device-app-data-base.ts │ └── appbuilder-device-app-data-base.ts ├── providers │ ├── livesync-provider.ts │ ├── appbuilder-livesync-provider-base.ts │ └── project-files-provider.ts ├── proton-static-config.ts ├── project │ ├── project.ts │ ├── cordova-project-capabilities.ts │ └── nativescript-project-capabilities.ts ├── mobile-platforms-capabilities.ts ├── appbuilder-bootstrap.ts ├── device-log-provider.ts ├── proton-bootstrap.ts ├── project-constants.ts └── services │ ├── livesync │ ├── android-livesync-service.ts │ └── companion-apps-service.ts │ └── path-filtering.ts ├── queue.ts ├── utils.ts ├── resource-loader.ts ├── progress-indicator.ts ├── command-params.ts ├── codeGeneration ├── code-generation.d.ts ├── code-entity.ts └── code-printer.ts ├── tslint.json ├── project-helper.ts ├── dispatchers.ts ├── decorators.ts ├── package.json └── verify-node-version.ts /definitions/watchr.d.ts: -------------------------------------------------------------------------------- 1 | interface IWatcherInstance { 2 | close: () => void; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /bin/common-lib.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | require("../common-lib"); 6 | -------------------------------------------------------------------------------- /common-lib.ts: -------------------------------------------------------------------------------- 1 | require("./appbuilder/proton-bootstrap"); 2 | 3 | module.exports = $injector.publicApi; 4 | -------------------------------------------------------------------------------- /docs/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /definitions/iconv-lite.d.ts: -------------------------------------------------------------------------------- 1 | declare module "iconv-lite" { 2 | export function extendNodeEncodings(): void; 3 | } 4 | -------------------------------------------------------------------------------- /definitions/osenv.d.ts: -------------------------------------------------------------------------------- 1 | declare module "osenv" { 2 | function home(): string; 3 | function shell(): string; 4 | } 5 | -------------------------------------------------------------------------------- /definitions/byline.d.ts: -------------------------------------------------------------------------------- 1 | declare module "byline" { 2 | function lineStream(stream: any): any; 3 | export = lineStream; 4 | } -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Bold-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Light-webfont.ttf -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --reporter spec-xunit-file 3 | --require test/test-bootstrap.js 4 | --timeout 3000 5 | test/unit-tests -------------------------------------------------------------------------------- /test/resources/sampleZipFileTest.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/test/resources/sampleZipFileTest.zip -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Italic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Regular-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /definitions/open.d.ts: -------------------------------------------------------------------------------- 1 | declare module "open" { 2 | function open(target: string, appname: string): void; 3 | 4 | export = open; 5 | } 6 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /resources/platform-tools/android/linux/adb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/resources/platform-tools/android/linux/adb -------------------------------------------------------------------------------- /resources/platform-tools/android/darwin/adb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/resources/platform-tools/android/darwin/adb -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/docs/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /resources/platform-tools/android/win32/adb.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/resources/platform-tools/android/win32/adb.exe -------------------------------------------------------------------------------- /resources/platform-tools/unzip/win32/unzip.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/resources/platform-tools/unzip/win32/unzip.exe -------------------------------------------------------------------------------- /definitions/plist.d.ts: -------------------------------------------------------------------------------- 1 | declare module "plist" { 2 | export function parse(data: any): IDictionary; 3 | export function build(data: any): string; 4 | } 5 | -------------------------------------------------------------------------------- /resources/platform-tools/android/win32/AdbWinApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/resources/platform-tools/android/win32/AdbWinApi.dll -------------------------------------------------------------------------------- /resources/platform-tools/android/win32/fastboot.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/resources/platform-tools/android/win32/fastboot.exe -------------------------------------------------------------------------------- /resources/platform-tools/android/win32/AdbWinUsbApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telerik/mobile-cli-lib/HEAD/resources/platform-tools/android/win32/AdbWinUsbApi.dll -------------------------------------------------------------------------------- /definitions/validator.d.ts: -------------------------------------------------------------------------------- 1 | interface IValidationResult { 2 | error: string; 3 | isSuccessful: boolean; 4 | } 5 | 6 | interface IProjectNameValidator { 7 | validate(name: string): boolean; 8 | } 9 | -------------------------------------------------------------------------------- /vendor/License.txt: -------------------------------------------------------------------------------- 1 | Please access the license terms currently located here: http://www.telerik.com/analytics/terms-of-service 2 | 3 | Please contact sales@telerik.com for further information 4 | 5 | -------------------------------------------------------------------------------- /mobile/logging-levels.ts: -------------------------------------------------------------------------------- 1 | export class LoggingLevels implements Mobile.ILoggingLevels { 2 | public info = "INFO"; 3 | public full = "FULL"; 4 | } 5 | $injector.register("loggingLevels", LoggingLevels); 6 | -------------------------------------------------------------------------------- /definitions/simple-plist.d.ts: -------------------------------------------------------------------------------- 1 | declare module "simple-plist" { 2 | export function readFile(filePath: string, callback?:(err: Error, obj: any) => void): void; 3 | export function readFileSync(filePath: string): any; 4 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/*.ts 2 | **/*.js.map 3 | coverage 4 | .tscache 5 | test-reports.xml 6 | *.tgz 7 | .vscode 8 | .npmignore 9 | test/**/* 10 | test-scripts/**/* 11 | lib/common/test/**/* 12 | lib/common/test-scripts/**/* 13 | -------------------------------------------------------------------------------- /doctor.d.ts: -------------------------------------------------------------------------------- 1 | import * as doctor from "nativescript-doctor"; 2 | 3 | declare global { 4 | interface ISysInfoConfig extends NativeScriptDoctor.ISysInfoConfig { } 5 | interface ISysInfoData extends NativeScriptDoctor.ISysInfoData { } 6 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.DS_Store": true, 6 | "**/*.js": { "when": "$(basename).ts"}, 7 | "**/*.js.map": true 8 | } 9 | } -------------------------------------------------------------------------------- /opener.ts: -------------------------------------------------------------------------------- 1 | import xopen = require("open"); 2 | 3 | export class Opener implements IOpener { 4 | 5 | public open(target: string, appname?: string): any { 6 | return xopen(target, appname); 7 | } 8 | } 9 | $injector.register("opener", Opener); 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | - release 5 | language: node_js 6 | node_js: 7 | - '6' 8 | install: 9 | - npm install --ignore-scripts 10 | - npm install --save-dev grunt-cli 11 | script: 12 | - node_modules/.bin/grunt all 13 | -------------------------------------------------------------------------------- /definitions/user-settings.d.ts: -------------------------------------------------------------------------------- 1 | declare module UserSettings { 2 | interface IUserSettingsService { 3 | getSettingValue(settingName: string): Promise; 4 | saveSetting(key: string, value: T): Promise; 5 | removeSetting(key: string): Promise; 6 | } 7 | } -------------------------------------------------------------------------------- /mobile/device-platforms-constants.ts: -------------------------------------------------------------------------------- 1 | export class DevicePlatformsConstants implements Mobile.IDevicePlatformsConstants { 2 | public iOS = "iOS"; 3 | public Android = "Android"; 4 | public WP8 = "WP8"; 5 | } 6 | $injector.register("devicePlatformsConstants", DevicePlatformsConstants); 7 | -------------------------------------------------------------------------------- /definitions/rimraf.d.ts: -------------------------------------------------------------------------------- 1 | declare module "rimraf" { 2 | function rimraf(path: string, callback: (error: Error) => void): void; 3 | namespace rimraf { 4 | export function sync(path: string): void; 5 | export var EMFILE_MAX: number; 6 | export var BUSYTRIES_MAX: number; 7 | } 8 | export = rimraf; 9 | } 10 | -------------------------------------------------------------------------------- /services/analytics/google-analytics-custom-dimensions.d.ts: -------------------------------------------------------------------------------- 1 | declare const enum GoogleAnalyticsCustomDimensions { 2 | cliVersion = "cd1", 3 | projectType = "cd2", 4 | clientID = "cd3", 5 | sessionID = "cd4", 6 | client = "cd5", 7 | nodeVersion = "cd6", 8 | playgroundId = "cd7", 9 | usedTutorial = "cd8" 10 | } -------------------------------------------------------------------------------- /test/unit-tests/mocks/public-api-mocks.ts: -------------------------------------------------------------------------------- 1 | import { exported } from "../../../decorators"; 2 | 3 | export class TestPublicAPI { 4 | @exported("testPublicApi") 5 | public async myMethod(expectedResult: any): Promise { 6 | return expectedResult; 7 | } 8 | } 9 | $injector.register("testPublicApi", TestPublicAPI); 10 | -------------------------------------------------------------------------------- /messages/messages.interface.d.ts: -------------------------------------------------------------------------------- 1 | // 2 | // automatically generated code; do not edit manually! 3 | // 4 | interface IMessages{ 5 | Devices : { 6 | NotFoundDeviceByIdentifierErrorMessage: string; 7 | NotFoundDeviceByIdentifierErrorMessageWithIdentifier: string; 8 | NotFoundDeviceByIndexErrorMessage: string; 9 | }; 10 | 11 | } 12 | 13 | -------------------------------------------------------------------------------- /validators/validation-result.ts: -------------------------------------------------------------------------------- 1 | export class ValidationResult implements IValidationResult { 2 | public static Successful = new ValidationResult(null); 3 | 4 | constructor(private errorMsg: string) { } 5 | 6 | public get error(): string { 7 | return this.errorMsg; 8 | } 9 | 10 | public get isSuccessful(): boolean { 11 | return !this.errorMsg; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/node-args.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const getNodeArgs = () => { 4 | const nodeVersion = process.version; 5 | const requiresHarmonyFlagRegex = /^v[45]\./; 6 | const nodeArgs = []; 7 | 8 | if (requiresHarmonyFlagRegex.test(nodeVersion)) { 9 | nodeArgs.push("--harmony"); 10 | } 11 | 12 | return nodeArgs; 13 | }; 14 | 15 | module.exports.getNodeArgs = getNodeArgs; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "removeComments": false, 8 | "noImplicitAny": true, 9 | "experimentalDecorators": true, 10 | "alwaysStrict": true, 11 | "noUnusedLocals": true 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | "scratch", 16 | "coverage" 17 | ] 18 | } -------------------------------------------------------------------------------- /os-info.ts: -------------------------------------------------------------------------------- 1 | import * as os from "os"; 2 | 3 | export class OsInfo implements IOsInfo { 4 | public type(): string { 5 | return os.type(); 6 | } 7 | 8 | public release(): string { 9 | return os.release(); 10 | } 11 | 12 | public arch(): string { 13 | return os.arch(); 14 | } 15 | 16 | public platform(): string { 17 | return os.platform(); 18 | } 19 | } 20 | 21 | $injector.register("osInfo", OsInfo); 22 | -------------------------------------------------------------------------------- /mobile/device-app-data/device-app-data-base.ts: -------------------------------------------------------------------------------- 1 | import * as helpers from "../../helpers"; 2 | 3 | export class DeviceAppDataBase { 4 | constructor(protected _appIdentifier: string) { } 5 | 6 | public get appIdentifier(): string { 7 | return this._appIdentifier; 8 | } 9 | 10 | protected _getDeviceProjectRootPath(projectRoot: string): string { 11 | return helpers.fromWindowsRelativePathToUnix(projectRoot); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /definitions/temp.d.ts: -------------------------------------------------------------------------------- 1 | 2 | interface ITempPathOptions { 3 | prefix?: string; 4 | suffix?: string; 5 | } 6 | 7 | interface ITempFileInfo { 8 | path?: string; 9 | fd?: any; 10 | } 11 | 12 | declare module "temp" { 13 | function track(): void; 14 | function cleanup(): void; 15 | function mkdirSync(affixes: string): string; 16 | function path(options: ITempPathOptions): string; 17 | function open(name: string, fn: (error: Error, info: ITempFileInfo) => void): any; 18 | } 19 | -------------------------------------------------------------------------------- /commands/doctor.ts: -------------------------------------------------------------------------------- 1 | export class DoctorCommand implements ICommand { 2 | 3 | constructor(private $doctorService: IDoctorService, 4 | private $projectHelper: IProjectHelper) { } 5 | 6 | public allowedParameters: ICommandParameter[] = []; 7 | 8 | public execute(args: string[]): Promise { 9 | return this.$doctorService.printWarnings({ trackResult: false, projectDir: this.$projectHelper.projectDir }); 10 | } 11 | } 12 | 13 | $injector.registerCommand("doctor", DoctorCommand); 14 | -------------------------------------------------------------------------------- /config-base.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | export class ConfigBase implements Config.IConfig { 4 | DISABLE_HOOKS: boolean = false; 5 | 6 | constructor(protected $fs: IFileSystem) { } 7 | 8 | protected loadConfig(name: string): any { 9 | const configFileName = this.getConfigPath(name); 10 | return this.$fs.readJson(configFileName); 11 | } 12 | 13 | protected getConfigPath(filename: string): string { 14 | return path.join(__dirname, "../../config/", filename + ".json"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /definitions/cli-global.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines additional properties added to global object from CLI. 3 | */ 4 | interface ICliGlobal extends NodeJS.Global { 5 | /** 6 | * Lodash instance. 7 | */ 8 | _: any; 9 | 10 | /** 11 | * Eqatec analytics instance. 12 | */ 13 | _eqatec: IEqatec; 14 | 15 | /** 16 | * Global instance of the module used for dependency injection. 17 | */ 18 | $injector: IInjector; 19 | 20 | /** 21 | * Instance of xmlhttprequest. 22 | */ 23 | XMLHttpRequest: any; 24 | } 25 | -------------------------------------------------------------------------------- /docs/helpers/basic-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @MAN_PAGE_NAME@ 5 | 6 | 7 | 8 | 9 | 10 | 11 | @HTML_COMMAND_HELP@ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /definitions/node-uuid-cjs.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for node-uuid.js 2 | // Project: https://github.com/broofa/node-uuid 3 | // Definitions by: Jeff May 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | /// 7 | 8 | /** 9 | * Expose as CommonJS module 10 | * For use in node environment or browser environment (using webpack or other module loaders) 11 | */ 12 | declare module "uuid" { 13 | var uuid: __NodeUUID.UUID; 14 | export = uuid; 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !vendor/*.js 3 | 4 | *.js.map 5 | !vendor/*.js 6 | 7 | lib-cov 8 | *.seed 9 | *.log 10 | *.csv 11 | *.dat 12 | *.out 13 | *.pid 14 | *.gz 15 | *.tgz 16 | *.tmp 17 | *.sublime-workspace 18 | tscommand*.tmp.txt 19 | .tscache/ 20 | 21 | pids 22 | logs 23 | results 24 | scratch/ 25 | .idea/workspace.xml 26 | .idea/tasks.xml 27 | .idea/watcherTasks.xml 28 | 29 | npm-debug.log 30 | node_modules 31 | !Gruntfile.js 32 | .d.ts 33 | !bin/**/* 34 | coverage/**/* 35 | xunit.xml 36 | test-reports.xml 37 | !test-scripts/** 38 | !scripts/** 39 | -------------------------------------------------------------------------------- /test/unit-tests/mocks/decorators-cache.ts: -------------------------------------------------------------------------------- 1 | import { cache } from "../../../decorators"; 2 | 3 | export class CacheDecoratorsTest { 4 | public counter = 0; 5 | 6 | @cache() 7 | public method(num: number): number { 8 | this.counter++; 9 | return num; 10 | } 11 | 12 | @cache() 13 | public async promisifiedMethod(num: number): Promise { 14 | this.counter++; 15 | return num; 16 | } 17 | 18 | public _property = 0; 19 | 20 | @cache() 21 | public get property(): number { 22 | this.counter++; 23 | return this._property; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /commands/preuninstall.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | export class PreUninstallCommand implements ICommand { 4 | public disableAnalytics = true; 5 | 6 | public allowedParameters: ICommandParameter[] = []; 7 | 8 | constructor(private $fs: IFileSystem, 9 | private $settingsService: ISettingsService) { } 10 | 11 | public async execute(args: string[]): Promise { 12 | this.$fs.deleteFile(path.join(this.$settingsService.getProfileDir(), "KillSwitches", "cli")); 13 | } 14 | } 15 | 16 | $injector.registerCommand("dev-preuninstall", PreUninstallCommand); 17 | -------------------------------------------------------------------------------- /messages/messages.ts: -------------------------------------------------------------------------------- 1 | // 2 | // automatically generated code; do not edit manually! 3 | // 4 | /* tslint:disable:all */ 5 | export class Messages implements IMessages{ 6 | Devices = { 7 | NotFoundDeviceByIdentifierErrorMessage: "Devices.NotFoundDeviceByIdentifierErrorMessage", 8 | NotFoundDeviceByIdentifierErrorMessageWithIdentifier: "Devices.NotFoundDeviceByIdentifierErrorMessageWithIdentifier", 9 | NotFoundDeviceByIndexErrorMessage: "Devices.NotFoundDeviceByIndexErrorMessage", 10 | }; 11 | 12 | } 13 | $injector.register('messages', Messages); 14 | /* tslint:enable */ 15 | -------------------------------------------------------------------------------- /test-scripts/mocha.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const childProcess = require("child_process"); 4 | const path = require("path"); 5 | const pathToMocha = path.join(__dirname, "..", "node_modules", "mocha", "bin", "_mocha"); 6 | 7 | const nodeArgs = require("../scripts/node-args").getNodeArgs(); 8 | 9 | const args = nodeArgs.concat(pathToMocha); 10 | 11 | const nodeProcess = childProcess.spawn("node", args, { stdio: "inherit" }); 12 | nodeProcess.on("close", (code) => { 13 | // We need this handler so if any test fails, we'll exit with same exit code as mocha. 14 | process.exit(code); 15 | }); 16 | -------------------------------------------------------------------------------- /plist-parser.ts: -------------------------------------------------------------------------------- 1 | import * as simplePlist from "simple-plist"; 2 | 3 | export class PlistParser implements IPlistParser { 4 | public parseFile(plistFilePath: string): Promise { 5 | return new Promise((resolve, reject) => { 6 | simplePlist.readFile(plistFilePath, (err: Error, obj: any) => { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | resolve(obj); 11 | } 12 | }); 13 | }); 14 | } 15 | 16 | public parseFileSync(plistFilePath: string): any { 17 | return simplePlist.readFileSync(plistFilePath); 18 | } 19 | } 20 | $injector.register("plistParser", PlistParser); 21 | -------------------------------------------------------------------------------- /docs/helpers/basic-extensions-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @MAN_PAGE_NAME@ 5 | 6 | 7 | 8 | 9 | 10 | 11 |

@EXTENSION_NAME@ extension

12 | @HTML_COMMAND_HELP@ 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /definitions/colors.d.ts: -------------------------------------------------------------------------------- 1 | declare module "colors" { 2 | export function setTheme(theme: any): void; 3 | export function addSequencer(name: string, callback: Function): void; 4 | 5 | // none, browser, console 6 | export var mode: string; 7 | } 8 | 9 | interface String { 10 | // In ES6 there's a method called `bold` in String interface. 11 | // bold: String; 12 | italic: String; 13 | underline: String; 14 | inverse: String; 15 | white: String; 16 | grey: String; 17 | black: String; 18 | blue: String; 19 | cyan: String; 20 | green: String; 21 | magenta: String; 22 | red: String; 23 | yellow: String; 24 | } 25 | -------------------------------------------------------------------------------- /commands/proxy/proxy-get.ts: -------------------------------------------------------------------------------- 1 | import { ProxyCommandBase } from "./proxy-base"; 2 | 3 | const proxyGetCommandName = "proxy|*get"; 4 | 5 | export class ProxyGetCommand extends ProxyCommandBase { 6 | constructor(protected $analyticsService: IAnalyticsService, 7 | protected $logger: ILogger, 8 | protected $proxyService: IProxyService) { 9 | super($analyticsService, $logger, $proxyService, proxyGetCommandName); 10 | } 11 | 12 | public async execute(args: string[]): Promise { 13 | this.$logger.out(await this.$proxyService.getInfo()); 14 | await this.tryTrackUsage(); 15 | } 16 | } 17 | 18 | $injector.registerCommand(proxyGetCommandName, ProxyGetCommand); 19 | -------------------------------------------------------------------------------- /appbuilder/mobile/appbuilder-companion-device-app-data-base.ts: -------------------------------------------------------------------------------- 1 | import { AppBuilderDeviceAppDataBase } from "./appbuilder-device-app-data-base"; 2 | 3 | export abstract class AppBuilderCompanionDeviceAppDataBase extends AppBuilderDeviceAppDataBase { 4 | public isLiveSyncSupported(): Promise { 5 | return this.device.applicationManager.isApplicationInstalled(this.appIdentifier); 6 | } 7 | 8 | public getLiveSyncNotSupportedError(): string { 9 | return `Cannot LiveSync changes to the ${this.getCompanionAppName()}. The ${this.getCompanionAppName()} is not installed on ${this.device.deviceInfo.identifier}.`; 10 | } 11 | 12 | protected abstract getCompanionAppName(): string; 13 | } 14 | -------------------------------------------------------------------------------- /resources/messages/errorMessages.json: -------------------------------------------------------------------------------- 1 | { 2 | "Devices": { 3 | "NotFoundDeviceByIdentifierErrorMessage": "Cannot resolve the specified connected device by the provided index or identifier. To list currently connected devices and verify that the specified index or identifier exists, run '%s device'.", 4 | "NotFoundDeviceByIdentifierErrorMessageWithIdentifier": "Could not find device by specified identifier '%s'. To list currently connected devices and verify that the specified identifier exists, run '%s device'.", 5 | "NotFoundDeviceByIndexErrorMessage": "Could not find device by specified index %d. To list currently connected devices and verify that the specified index exists, run '%s device'." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mobile/ios/simulator/ios-sim-resolver.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | export class IOSSimResolver implements Mobile.IiOSSimResolver { 4 | private static iOSSimName = "ios-sim-portable"; 5 | private static iOSStandaloneExecutableName = "ios-sim-standalone.js"; 6 | 7 | private _iOSSim: any = null; 8 | public get iOSSim(): any { 9 | if (!this._iOSSim) { 10 | this._iOSSim = require(IOSSimResolver.iOSSimName); 11 | } 12 | 13 | return this._iOSSim; 14 | } 15 | 16 | public get iOSSimPath(): string { 17 | return path.join(require.resolve(IOSSimResolver.iOSSimName), "..", IOSSimResolver.iOSStandaloneExecutableName); 18 | } 19 | } 20 | 21 | $injector.register("iOSSimResolver", IOSSimResolver); 22 | -------------------------------------------------------------------------------- /commands/proxy/proxy-base.ts: -------------------------------------------------------------------------------- 1 | export abstract class ProxyCommandBase implements ICommand { 2 | public disableAnalytics = true; 3 | public allowedParameters: ICommandParameter[] = []; 4 | 5 | constructor(protected $analyticsService: IAnalyticsService, 6 | protected $logger: ILogger, 7 | protected $proxyService: IProxyService, 8 | private commandName: string) { 9 | } 10 | 11 | public abstract execute(args: string[]): Promise; 12 | 13 | protected async tryTrackUsage() { 14 | try { 15 | await this.$analyticsService.trackFeature(this.commandName); 16 | } catch (ex) { 17 | this.$logger.trace("Error in trying to track proxy command usage:"); 18 | this.$logger.trace(ex); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /commands/proxy/proxy-clear.ts: -------------------------------------------------------------------------------- 1 | import { ProxyCommandBase } from "./proxy-base"; 2 | const proxyClearCommandName = "proxy|clear"; 3 | 4 | export class ProxyClearCommand extends ProxyCommandBase { 5 | constructor(protected $analyticsService: IAnalyticsService, 6 | protected $logger: ILogger, 7 | protected $proxyService: IProxyService) { 8 | super($analyticsService, $logger, $proxyService, proxyClearCommandName); 9 | } 10 | 11 | public async execute(args: string[]): Promise { 12 | await this.$proxyService.clearCache(); 13 | this.$logger.out("Successfully cleared proxy."); 14 | await this.tryTrackUsage(); 15 | } 16 | } 17 | 18 | $injector.registerCommand(proxyClearCommandName, ProxyClearCommand); 19 | -------------------------------------------------------------------------------- /queue.ts: -------------------------------------------------------------------------------- 1 | export class Queue implements IQueue { 2 | private promiseResolve: (value?: void | PromiseLike) => void; 3 | 4 | public constructor(private items?: T[]) { 5 | this.items = this.items === undefined ? [] : this.items; 6 | } 7 | 8 | public enqueue(item: T): void { 9 | this.items.unshift(item); 10 | 11 | if (this.promiseResolve) { 12 | this.promiseResolve(); 13 | } 14 | } 15 | 16 | public async dequeue(): Promise { 17 | if (!this.items.length) { 18 | const promise = new Promise((resolve, reject) => { 19 | this.promiseResolve = resolve; 20 | }); 21 | 22 | await promise; 23 | 24 | this.promiseResolve = null; 25 | } 26 | 27 | return this.items.pop(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /appbuilder/providers/livesync-provider.ts: -------------------------------------------------------------------------------- 1 | import { AppBuilderLiveSyncProviderBase } from "./appbuilder-livesync-provider-base"; 2 | 3 | export class LiveSyncProvider extends AppBuilderLiveSyncProviderBase { 4 | constructor($androidLiveSyncServiceLocator: { factory: Function }, 5 | $iosLiveSyncServiceLocator: { factory: Function }) { 6 | super($androidLiveSyncServiceLocator, $iosLiveSyncServiceLocator); 7 | } 8 | 9 | public async buildForDevice(device: Mobile.IDevice): Promise { 10 | throw new Error(`Application is not installed on device ${device.deviceInfo.identifier}. Cannot LiveSync changes without installing the application before that.`); 11 | } 12 | } 13 | $injector.register("liveSyncProvider", LiveSyncProvider); 14 | -------------------------------------------------------------------------------- /services/qr.ts: -------------------------------------------------------------------------------- 1 | import { imageSync } from "qr-image"; 2 | import { escape } from "querystring"; 3 | 4 | export class QrCodeGenerator implements IQrCodeGenerator { 5 | constructor(private $staticConfig: Config.IStaticConfig, 6 | private $logger: ILogger) { } 7 | 8 | public async generateDataUri(data: string): Promise { 9 | let result: string = null; 10 | try { 11 | const qrSvg = imageSync(data, { size: this.$staticConfig.QR_SIZE, type: "svg" }).toString(); 12 | result = `data:image/svg+xml;utf-8,${escape(qrSvg)}`; 13 | } catch (err) { 14 | this.$logger.trace(`Failed to generate QR code for ${data}`, err); 15 | } 16 | 17 | return result; 18 | } 19 | } 20 | 21 | $injector.register("qr", QrCodeGenerator); 22 | -------------------------------------------------------------------------------- /definitions/logger.d.ts: -------------------------------------------------------------------------------- 1 | interface ILogger { 2 | setLevel(level: string): void; 3 | getLevel(): string; 4 | fatal(formatStr?: any, ...args: any[]): void; 5 | error(formatStr?: any, ...args: any[]): void; 6 | warn(formatStr?: any, ...args: any[]): void; 7 | warnWithLabel(formatStr?: any, ...args: any[]): void; 8 | info(formatStr?: any, ...args: any[]): void; 9 | debug(formatStr?: any, ...args: any[]): void; 10 | trace(formatStr?: any, ...args: any[]): void; 11 | printMarkdown(...args: any[]): void; 12 | 13 | out(formatStr?: any, ...args: any[]): void; 14 | write(...args: any[]): void; 15 | 16 | prepare(item: any): string; 17 | printInfoMessageOnSameLine(message: string): void; 18 | printMsgWithTimeout(message: string, timeout: number): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /commands/device/uninstall-application.ts: -------------------------------------------------------------------------------- 1 | export class UninstallApplicationCommand implements ICommand { 2 | constructor(private $devicesService: Mobile.IDevicesService, 3 | private $stringParameter: ICommandParameter, 4 | private $options: ICommonOptions) { } 5 | 6 | allowedParameters: ICommandParameter[] = [this.$stringParameter]; 7 | 8 | public async execute(args: string[]): Promise { 9 | await this.$devicesService.initialize({ deviceId: this.$options.device, skipInferPlatform: true }); 10 | 11 | const action = (device: Mobile.IDevice) => device.applicationManager.uninstallApplication(args[0]); 12 | await this.$devicesService.execute(action); 13 | } 14 | } 15 | $injector.registerCommand(["device|uninstall", "devices|uninstall"], UninstallApplicationCommand); 16 | -------------------------------------------------------------------------------- /test-scripts/istanbul.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const childProcess = require("child_process"); 4 | const path = require("path"); 5 | const pathToNodeModules = path.join(__dirname, "..", "node_modules"); 6 | const pathToIstanbul = path.join(pathToNodeModules, "istanbul", "lib", "cli.js"); 7 | const pathToMocha = path.join(pathToNodeModules, "mocha", "bin", "_mocha"); 8 | 9 | const istanbulArgs = [ pathToIstanbul, "cover", pathToMocha ]; 10 | 11 | const nodeArgs = require("../scripts/node-args").getNodeArgs(); 12 | 13 | const args = nodeArgs.concat(istanbulArgs); 14 | 15 | const nodeProcess = childProcess.spawn("node", args, { stdio: "inherit" }); 16 | nodeProcess.on("close", (code) => { 17 | // We need this handler so if any test fails, we'll exit with same exit code as istanbul. 18 | process.exit(code); 19 | }); -------------------------------------------------------------------------------- /utils.ts: -------------------------------------------------------------------------------- 1 | export class Utils implements IUtils { 2 | constructor(private $options: ICommonOptions, 3 | private $logger: ILogger) { } 4 | 5 | public getParsedTimeout(defaultTimeout: number): number { 6 | let timeout = defaultTimeout; 7 | if (this.$options.timeout) { 8 | const parsedValue = parseInt(this.$options.timeout); 9 | if (!isNaN(parsedValue) && parsedValue >= 0) { 10 | timeout = parsedValue; 11 | } else { 12 | this.$logger.warn("Specify timeout in a number of seconds to wait. Default value: " + timeout + " seconds will be used."); 13 | } 14 | } 15 | 16 | return timeout; 17 | } 18 | 19 | public getMilliSecondsTimeout(defaultTimeout: number): number { 20 | const timeout = this.getParsedTimeout(defaultTimeout); 21 | return timeout * 1000; 22 | } 23 | } 24 | $injector.register("utils", Utils); 25 | -------------------------------------------------------------------------------- /definitions/eqatec.d.ts: -------------------------------------------------------------------------------- 1 | interface IEqatecLogging { 2 | logMessage(...args: string[]): void; 3 | logError(...args: string[]): void; 4 | } 5 | 6 | interface IEqatecSettings { 7 | useHttps: boolean; 8 | userAgent: string; 9 | version: string; 10 | useCookies: boolean; 11 | loggingInterface: IEqatecLogging; 12 | } 13 | 14 | interface IEqatecMonitor { 15 | trackFeature(featureNameAndValue: string): void; 16 | trackException(exception: Error, message: string): void; 17 | stop(): void; 18 | setInstallationID(guid: string): void; 19 | setUserID(userId: string): void; 20 | setStartCount(count: number): void; 21 | start(): void; 22 | status(): { isSending: boolean }; 23 | } 24 | 25 | interface IEqatec { 26 | createSettings(analyticsProjectKey: string): IEqatecSettings; 27 | createMonitor(settings: IEqatecSettings): IEqatecMonitor; 28 | } -------------------------------------------------------------------------------- /mobile/device-log-provider.ts: -------------------------------------------------------------------------------- 1 | import { DeviceLogProviderBase } from "./device-log-provider-base"; 2 | 3 | export class DeviceLogProvider extends DeviceLogProviderBase { 4 | constructor(protected $logFilter: Mobile.ILogFilter, 5 | protected $logger: ILogger) { 6 | super($logFilter, $logger); 7 | } 8 | 9 | public logData(lineText: string, platform: string, deviceIdentifier: string): void { 10 | const loggingOptions = this.getDeviceLogOptionsForDevice(deviceIdentifier); 11 | const data = this.$logFilter.filterData(platform, lineText, loggingOptions); 12 | if (data) { 13 | this.$logger.write(data); 14 | } 15 | } 16 | 17 | public setLogLevel(logLevel: string, deviceIdentifier?: string): void { 18 | this.$logFilter.loggingLevel = logLevel.toUpperCase(); 19 | } 20 | } 21 | $injector.register("deviceLogProvider", DeviceLogProvider); 22 | -------------------------------------------------------------------------------- /definitions/xmlhttprequest.d.ts: -------------------------------------------------------------------------------- 1 | declare module "xmlhttprequest" { 2 | export interface XMLHttpRequest extends Function { 3 | withCredentials: boolean; 4 | UNSENT: number; 5 | OPENED: number; 6 | HEADERS_RECEIVED: number; 7 | LOADING: number; 8 | DONE: number; 9 | readyState: number; 10 | onreadystatechange: Function; 11 | responseText: string; 12 | responseXML: string; 13 | status: number; 14 | statusText: string; 15 | setTimeout: Function; 16 | open: Function; 17 | setDisableHeaderCheck: Function; 18 | setRequestHeader: Function; 19 | getResponseHeader: Function; 20 | getAllResponseHeaders: Function; 21 | getRequestHeader: Function; 22 | send: Function; 23 | handleError: Function; 24 | abort: Function; 25 | addEventListener: Function; 26 | removeEventListener: Function; 27 | dispatchEvent: [Function] 28 | } 29 | } -------------------------------------------------------------------------------- /appbuilder/proton-static-config.ts: -------------------------------------------------------------------------------- 1 | import { StaticConfigBase } from "../static-config-base"; 2 | import * as path from "path"; 3 | 4 | export class ProtonStaticConfig extends StaticConfigBase { 5 | constructor($injector: IInjector) { 6 | super($injector); 7 | } 8 | 9 | public async getAdbFilePath(): Promise { 10 | const value = await super.getAdbFilePath(); 11 | return value.replace("app.asar", "app.asar.unpacked"); 12 | } 13 | 14 | public get PATH_TO_BOOTSTRAP(): string { 15 | return path.join(__dirname, "proton-bootstrap"); 16 | } 17 | 18 | public disableAnalytics = true; 19 | 20 | public CLIENT_NAME = "Desktop Client - Universal"; 21 | 22 | public ANALYTICS_EXCEPTIONS_API_KEY: string = null; 23 | 24 | public PROFILE_DIR_NAME: string = ".appbuilder-desktop-universal"; 25 | } 26 | $injector.register("staticConfig", ProtonStaticConfig); 27 | -------------------------------------------------------------------------------- /commands/device/stop-application.ts: -------------------------------------------------------------------------------- 1 | export class StopApplicationOnDeviceCommand implements ICommand { 2 | 3 | constructor(private $devicesService: Mobile.IDevicesService, 4 | private $stringParameter: ICommandParameter, 5 | private $options: ICommonOptions) { } 6 | 7 | allowedParameters: ICommandParameter[] = [this.$stringParameter, this.$stringParameter, this.$stringParameter]; 8 | 9 | public async execute(args: string[]): Promise { 10 | await this.$devicesService.initialize({ deviceId: this.$options.device, skipInferPlatform: true, platform: args[1] }); 11 | 12 | const action = (device: Mobile.IDevice) => device.applicationManager.stopApplication({ appId: args[0], projectName: args[2] }); 13 | await this.$devicesService.execute(action); 14 | } 15 | } 16 | 17 | $injector.registerCommand(["device|stop", "devices|stop"], StopApplicationOnDeviceCommand); 18 | -------------------------------------------------------------------------------- /mobile/device-app-data/device-app-data-factory.ts: -------------------------------------------------------------------------------- 1 | export class DeviceAppDataFactory implements Mobile.IDeviceAppDataFactory { 2 | constructor(private $deviceAppDataProvider: Mobile.IDeviceAppDataProvider, 3 | private $injector: IInjector, 4 | private $options: ICommonOptions) { } 5 | 6 | create(appIdentifier: string, platform: string, device: Mobile.IDevice, liveSyncOptions?: { isForCompanionApp: boolean }): T { 7 | const factoryRules = this.$deviceAppDataProvider.createFactoryRules(); 8 | const isForCompanionApp = (liveSyncOptions && liveSyncOptions.isForCompanionApp) || this.$options.companion; 9 | const ctor = (factoryRules[platform])[isForCompanionApp ? "companion" : "vanilla"]; 10 | return this.$injector.resolve(ctor, { _appIdentifier: appIdentifier, device: device, platform: platform }); 11 | } 12 | } 13 | $injector.register("deviceAppDataFactory", DeviceAppDataFactory); 14 | -------------------------------------------------------------------------------- /services/plugins/npm-plugins-source.ts: -------------------------------------------------------------------------------- 1 | import { PluginsSourceBase } from "./plugins-source-base"; 2 | 3 | export class NpmPluginsSource extends PluginsSourceBase implements IPluginsSource { 4 | constructor(protected $progressIndicator: IProgressIndicator, 5 | protected $logger: ILogger, 6 | private $npmService: INpmService) { 7 | super($progressIndicator, $logger); 8 | } 9 | 10 | protected get progressIndicatorMessage(): string { 11 | return "Searching for plugins with npm search command."; 12 | } 13 | 14 | public async getPlugins(page: number, count: number): Promise { 15 | const skip = page * count; 16 | 17 | return _.slice(this.plugins, skip, skip + count); 18 | } 19 | 20 | protected async initializeCore(projectDir: string, keywords: string[]): Promise { 21 | this.plugins = await this.$npmService.search(this.projectDir, keywords); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /appbuilder/project/project.ts: -------------------------------------------------------------------------------- 1 | /// 2 | "use strict"; 3 | 4 | import { ProjectBase } from "./project-base"; 5 | 6 | export class Project extends ProjectBase { 7 | constructor(protected $cordovaProjectCapabilities: Project.ICapabilities, 8 | protected $errors: IErrors, 9 | protected $fs: IFileSystem, 10 | protected $logger: ILogger, 11 | protected $nativeScriptProjectCapabilities: Project.ICapabilities, 12 | protected $options: ICommonOptions, 13 | protected $projectConstants: Project.IConstants, 14 | protected $staticConfig: Config.IStaticConfig) { 15 | super($cordovaProjectCapabilities, $errors, $fs, $logger, $nativeScriptProjectCapabilities, $options, $projectConstants, $staticConfig); 16 | } 17 | 18 | protected validate(): void { /* Currently unused */ } 19 | protected saveProjectIfNeeded(): void { /* Currently unused */ } 20 | } 21 | $injector.register("project", Project); 22 | -------------------------------------------------------------------------------- /definitions/commands-service.d.ts: -------------------------------------------------------------------------------- 1 | interface ICommandsService { 2 | currentCommandData: ICommandData; 3 | allCommands(opts: {includeDevCommands: boolean}): string[]; 4 | tryExecuteCommand(commandName: string, commandArguments: string[]): Promise; 5 | executeCommandUnchecked(commandName: string, commandArguments: string[]): Promise; 6 | completeCommand(): Promise; 7 | } 8 | 9 | interface ICommandsServiceProvider { 10 | dynamicCommandsPrefix: string; 11 | getDynamicCommands(): Promise; 12 | generateDynamicCommands(): Promise; 13 | registerDynamicSubCommands(): void; 14 | } 15 | 16 | /** 17 | * Describes the command data. 18 | */ 19 | interface ICommandData { 20 | /** 21 | * Name of the command, usually the one registered in bootstrap. 22 | */ 23 | commandName: string; 24 | 25 | /** 26 | * Additional arguments passed to the command. 27 | */ 28 | commandArguments: string[]; 29 | } 30 | -------------------------------------------------------------------------------- /mobile/ios/ios-log-filter.ts: -------------------------------------------------------------------------------- 1 | export class IOSLogFilter implements Mobile.IPlatformLogFilter { 2 | protected infoFilterRegex = /^.*?(AppBuilder|Cordova|NativeScript).*?(:.*?|:.*?|:.*?)$/im; 3 | 4 | constructor(private $loggingLevels: Mobile.ILoggingLevels) { } 5 | 6 | public filterData(data: string, loggingOptions: Mobile.IDeviceLogOptions = {}): string { 7 | const specifiedLogLevel = (loggingOptions.logLevel || '').toUpperCase(); 8 | const pid = loggingOptions && loggingOptions.applicationPid; 9 | 10 | if (specifiedLogLevel === this.$loggingLevels.info && data) { 11 | if (pid) { 12 | return data.indexOf(`[${pid}]`) !== -1 ? data.trim() : null; 13 | } 14 | 15 | const matchingInfoMessage = data.match(this.infoFilterRegex); 16 | return matchingInfoMessage ? matchingInfoMessage[2] : null; 17 | } 18 | 19 | return data; 20 | } 21 | } 22 | 23 | $injector.register("iOSLogFilter", IOSLogFilter); 24 | -------------------------------------------------------------------------------- /resource-loader.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | export class ResourceLoader implements IResourceLoader { 4 | constructor(private $fs: IFileSystem, 5 | private $staticConfig: Config.IStaticConfig) { } 6 | 7 | resolvePath(resourcePath: string): string { 8 | return path.join(this.$staticConfig.RESOURCE_DIR_PATH, resourcePath); 9 | } 10 | 11 | openFile(resourcePath: string): NodeJS.ReadableStream { 12 | return this.$fs.createReadStream(this.resolvePath(resourcePath)); 13 | } 14 | 15 | readText(resourcePath: string): string { 16 | return this.$fs.readText(this.resolvePath(resourcePath)); 17 | } 18 | 19 | readJson(resourcePath: string): any { 20 | return this.$fs.readJson(this.resolvePath(resourcePath)); 21 | } 22 | 23 | public getPathToAppResources(framework: string): string { 24 | return path.join(this.resolvePath(framework), this.$staticConfig.APP_RESOURCES_DIR_NAME); 25 | } 26 | } 27 | $injector.register("resources", ResourceLoader); 28 | -------------------------------------------------------------------------------- /progress-indicator.ts: -------------------------------------------------------------------------------- 1 | import { isInteractive } from './helpers'; 2 | 3 | export class ProgressIndicator implements IProgressIndicator { 4 | constructor(private $logger: ILogger) { } 5 | 6 | public async showProgressIndicator(promise: Promise, timeout: number, options?: { surpressTrailingNewLine?: boolean }): Promise { 7 | const surpressTrailingNewLine = options && options.surpressTrailingNewLine; 8 | 9 | let isFulfilled = false; 10 | 11 | const tempPromise = new Promise((resolve, reject) => { 12 | promise 13 | .then(res => { 14 | isFulfilled = true; 15 | resolve(res); 16 | }) 17 | .catch(err => { 18 | isFulfilled = true; 19 | reject(err); 20 | }); 21 | }); 22 | 23 | if (!isInteractive()) { 24 | while (!isFulfilled) { 25 | await this.$logger.printMsgWithTimeout(".", timeout); 26 | } 27 | } 28 | 29 | if (!surpressTrailingNewLine) { 30 | this.$logger.out(); 31 | } 32 | 33 | return tempPromise; 34 | } 35 | } 36 | $injector.register("progressIndicator", ProgressIndicator); 37 | -------------------------------------------------------------------------------- /command-params.ts: -------------------------------------------------------------------------------- 1 | export class StringCommandParameter implements ICommandParameter { 2 | public mandatory = false; 3 | public errorMessage: string; 4 | 5 | constructor(private $injector: IInjector) { } 6 | 7 | public async validate(validationValue: string): Promise { 8 | if (!validationValue) { 9 | if (this.errorMessage) { 10 | this.$injector.resolve("errors").fail(this.errorMessage); 11 | } 12 | 13 | return false; 14 | } 15 | 16 | return true; 17 | } 18 | } 19 | $injector.register("stringParameter", StringCommandParameter); 20 | 21 | export class StringParameterBuilder implements IStringParameterBuilder { 22 | constructor(private $injector: IInjector) { } 23 | 24 | public createMandatoryParameter(errorMsg: string): ICommandParameter { 25 | const commandParameter = new StringCommandParameter(this.$injector); 26 | commandParameter.mandatory = true; 27 | commandParameter.errorMessage = errorMsg; 28 | 29 | return commandParameter; 30 | } 31 | } 32 | $injector.register("stringParameterBuilder", StringParameterBuilder); 33 | -------------------------------------------------------------------------------- /codeGeneration/code-generation.d.ts: -------------------------------------------------------------------------------- 1 | declare module CodeGeneration { 2 | 3 | interface IModel { 4 | id: string; 5 | properties: IDictionary; 6 | } 7 | 8 | interface IModelProperty { 9 | type: string; 10 | items: IModelPropertyItems; 11 | allowableValues: IModelPropertyValue; 12 | } 13 | 14 | interface IModelPropertyItems { 15 | $ref: string; 16 | } 17 | 18 | interface IModelPropertyValue { 19 | valueType: string; 20 | values: string[]; 21 | } 22 | 23 | interface ICodeEntity { 24 | opener?: string; 25 | codeEntityType: any; 26 | } 27 | 28 | interface ILine extends ICodeEntity { 29 | content: string; 30 | } 31 | 32 | interface IBlock extends ICodeEntity { 33 | opener: string; 34 | codeEntities: ICodeEntity[]; 35 | writeLine(content: string): void; 36 | addLine(line: ILine): void; 37 | addBlock(block: IBlock): void; 38 | addBlocks(blocks: IBlock[]): void; 39 | endingCharacter?: string; 40 | } 41 | 42 | interface IService { 43 | serviceInterface: IBlock; 44 | serviceImplementation: IBlock; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /appbuilder/mobile-platforms-capabilities.ts: -------------------------------------------------------------------------------- 1 | export class MobilePlatformsCapabilities implements Mobile.IPlatformsCapabilities { 2 | private platformCapabilities: IDictionary; 3 | 4 | public getPlatformNames(): string[] { 5 | return _.keys(this.getAllCapabilities()); 6 | } 7 | 8 | public getAllCapabilities(): IDictionary { 9 | this.platformCapabilities = this.platformCapabilities || { 10 | iOS: { 11 | wirelessDeploy: true, 12 | cableDeploy: true, 13 | companion: true, 14 | hostPlatformsForDeploy: ["win32", "darwin"] 15 | }, 16 | Android: { 17 | wirelessDeploy: true, 18 | cableDeploy: true, 19 | companion: true, 20 | hostPlatformsForDeploy: ["win32", "darwin", "linux"] 21 | }, 22 | WP8: { 23 | wirelessDeploy: true, 24 | cableDeploy: false, 25 | companion: true, 26 | hostPlatformsForDeploy: ["win32"] 27 | } 28 | }; 29 | 30 | return this.platformCapabilities; 31 | } 32 | } 33 | $injector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities); 34 | -------------------------------------------------------------------------------- /definitions/eqatec-analytics.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Describes the information that will be passed to analytics for tracking. 3 | */ 4 | interface ITrackingInformation { 5 | /** 6 | * The type of the data sent to analytics service - initalization data, feature to be tracked, error to be tracked, etc. 7 | */ 8 | type: TrackingTypes 9 | } 10 | 11 | /** 12 | * Describes information required for starting Eqatec Analytics tracking. 13 | */ 14 | interface IEqatecInitializeData extends ITrackingInformation { 15 | /** 16 | * The API key of the project in which we'll collect data. 17 | */ 18 | analyticsAPIKey: string; 19 | 20 | /** 21 | * The number of the current session in this project for this user. In case the value is 1, Analytics will count the user as new one. 22 | * Whenever a new session is started, we should increase the count. 23 | */ 24 | userSessionCount: number; 25 | 26 | /** 27 | * The installation identifier of analytics. It is unique per user. 28 | */ 29 | analyticsInstallationId: string; 30 | 31 | /** 32 | * The unique identifier of a user. 33 | */ 34 | userId: string; 35 | } 36 | -------------------------------------------------------------------------------- /services/micro-templating-service.ts: -------------------------------------------------------------------------------- 1 | import * as util from "util"; 2 | 3 | export class MicroTemplateService implements IMicroTemplateService { 4 | private dynamicCallRegex: RegExp; 5 | 6 | constructor(private $dynamicHelpService: IDynamicHelpService, 7 | private $injector: IInjector) { 8 | // Injector's dynamicCallRegex doesn't have 'g' option, which we need here. 9 | // Use ( ) in order to use $1 to get whole expression later 10 | this.dynamicCallRegex = new RegExp(util.format("(%s)", this.$injector.dynamicCallRegex.source), "g"); 11 | } 12 | 13 | public async parseContent(data: string, options: { isHtml: boolean }): Promise { 14 | const localVariables = this.$dynamicHelpService.getLocalVariables(options); 15 | const compiledTemplate = _.template(data.replace(this.dynamicCallRegex, "this.$injector.getDynamicCallData(\"$1\")")); 16 | // When debugging parsing, uncomment the line below: 17 | // console.log(compiledTemplate.source); 18 | return await compiledTemplate.apply(this, [localVariables]); 19 | } 20 | } 21 | $injector.register("microTemplateService", MicroTemplateService); 22 | -------------------------------------------------------------------------------- /mobile/mobile-core/device-discovery.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import { DeviceDiscoveryEventNames } from "../../constants"; 3 | 4 | export class DeviceDiscovery extends EventEmitter implements Mobile.IDeviceDiscovery { 5 | private devices: IDictionary = {}; 6 | 7 | public addDevice(device: Mobile.IDevice) { 8 | this.devices[device.deviceInfo.identifier] = device; 9 | this.raiseOnDeviceFound(device); 10 | } 11 | 12 | public removeDevice(deviceIdentifier: string) { 13 | const device = this.devices[deviceIdentifier]; 14 | if (!device) { 15 | return; 16 | } 17 | delete this.devices[deviceIdentifier]; 18 | this.raiseOnDeviceLost(device); 19 | } 20 | 21 | public async startLookingForDevices(): Promise { 22 | return; 23 | } 24 | 25 | private raiseOnDeviceFound(device: Mobile.IDevice) { 26 | this.emit(DeviceDiscoveryEventNames.DEVICE_FOUND, device); 27 | } 28 | 29 | private raiseOnDeviceLost(device: Mobile.IDevice) { 30 | this.emit(DeviceDiscoveryEventNames.DEVICE_LOST, device); 31 | } 32 | } 33 | $injector.register("deviceDiscovery", DeviceDiscovery); 34 | -------------------------------------------------------------------------------- /test/test-bootstrap.ts: -------------------------------------------------------------------------------- 1 | (global)._ = require("lodash"); 2 | (global).$injector = require("../yok").injector; 3 | $injector.require("hostInfo", "../host-info"); 4 | $injector.register("config", {}); 5 | 6 | // Our help reporting requires analyticsService. Give it this mock so that errors during test executions can be printed out 7 | $injector.register("analyticsService", { 8 | async checkConsent(): Promise { return ; }, 9 | async trackFeature(featureName: string): Promise { return ; }, 10 | async trackException(exception: any, message: string): Promise { return ; }, 11 | async setStatus(settingName: string, enabled: boolean): Promise { return ; }, 12 | async getStatusMessage(settingName: string, jsonFormat: boolean, readableSettingName: string): Promise { return "Fake message"; }, 13 | async isEnabled(settingName: string): Promise { return false; }, 14 | async track(featureName: string, featureValue: string): Promise { return ; } 15 | }); 16 | 17 | // Converts the js callstack to typescript 18 | import errors = require("../errors"); 19 | errors.installUncaughtExceptionListener(); 20 | -------------------------------------------------------------------------------- /commands/device/list-applications.ts: -------------------------------------------------------------------------------- 1 | import { EOL } from "os"; 2 | import * as util from "util"; 3 | 4 | export class ListApplicationsCommand implements ICommand { 5 | constructor(private $devicesService: Mobile.IDevicesService, 6 | private $logger: ILogger, 7 | private $options: ICommonOptions) { } 8 | 9 | allowedParameters: ICommandParameter[] = []; 10 | 11 | public async execute(args: string[]): Promise { 12 | await this.$devicesService.initialize({ deviceId: this.$options.device, skipInferPlatform: true }); 13 | const output: string[] = []; 14 | 15 | const action = async (device: Mobile.IDevice) => { 16 | const applications = await device.applicationManager.getInstalledApplications(); 17 | output.push(util.format("%s=====Installed applications on device with UDID '%s' are:", EOL, device.deviceInfo.identifier)); 18 | _.each(applications, (applicationId: string) => output.push(applicationId)); 19 | }; 20 | await this.$devicesService.execute(action); 21 | 22 | this.$logger.out(output.join(EOL)); 23 | } 24 | } 25 | $injector.registerCommand(["device|list-applications", "devices|list-applications"], ListApplicationsCommand); 26 | -------------------------------------------------------------------------------- /commands/device/put-file.ts: -------------------------------------------------------------------------------- 1 | export class PutFileCommand implements ICommand { 2 | constructor(private $devicesService: Mobile.IDevicesService, 3 | private $stringParameter: ICommandParameter, 4 | private $options: ICommonOptions, 5 | private $project: Project.IProjectBase, 6 | private $errors: IErrors) { } 7 | 8 | allowedParameters: ICommandParameter[] = [this.$stringParameter, this.$stringParameter, this.$stringParameter]; 9 | 10 | public async execute(args: string[]): Promise { 11 | await this.$devicesService.initialize({ deviceId: this.$options.device, skipInferPlatform: true }); 12 | let appIdentifier = args[2]; 13 | 14 | if (!appIdentifier && !this.$project.projectData) { 15 | this.$errors.failWithoutHelp("Please enter application identifier or execute this command in project."); 16 | } 17 | 18 | appIdentifier = appIdentifier || this.$project.projectData.AppIdentifier; 19 | const action = (device: Mobile.IDevice) => device.fileSystem.putFile(args[0], args[1], appIdentifier); 20 | await this.$devicesService.execute(action); 21 | } 22 | } 23 | $injector.registerCommand(["device|put-file", "devices|put-file"], PutFileCommand); 24 | -------------------------------------------------------------------------------- /commands/device/get-file.ts: -------------------------------------------------------------------------------- 1 | export class GetFileCommand implements ICommand { 2 | constructor(private $devicesService: Mobile.IDevicesService, 3 | private $stringParameter: ICommandParameter, 4 | private $project: Project.IProjectBase, 5 | private $errors: IErrors, 6 | private $options: ICommonOptions) { } 7 | 8 | public allowedParameters: ICommandParameter[] = [this.$stringParameter, this.$stringParameter]; 9 | 10 | public async execute(args: string[]): Promise { 11 | await this.$devicesService.initialize({ deviceId: this.$options.device, skipInferPlatform: true }); 12 | let appIdentifier = args[1]; 13 | 14 | if (!appIdentifier && !this.$project.projectData) { 15 | this.$errors.failWithoutHelp("Please enter application identifier or execute this command in project."); 16 | } 17 | 18 | appIdentifier = appIdentifier || this.$project.projectData.AppIdentifier; 19 | 20 | const action = (device: Mobile.IDevice) => device.fileSystem.getFile(args[0], appIdentifier, this.$options.file); 21 | await this.$devicesService.execute(action); 22 | } 23 | } 24 | 25 | $injector.registerCommand(["device|get-file", "devices|get-file"], GetFileCommand); 26 | -------------------------------------------------------------------------------- /commands/device/run-application.ts: -------------------------------------------------------------------------------- 1 | export class RunApplicationOnDeviceCommand implements ICommand { 2 | 3 | constructor(private $devicesService: Mobile.IDevicesService, 4 | private $errors: IErrors, 5 | private $stringParameter: ICommandParameter, 6 | private $staticConfig: Config.IStaticConfig, 7 | private $options: ICommonOptions) { } 8 | 9 | public allowedParameters: ICommandParameter[] = [this.$stringParameter, this.$stringParameter]; 10 | 11 | public async execute(args: string[]): Promise { 12 | await this.$devicesService.initialize({ deviceId: this.$options.device, skipInferPlatform: true }); 13 | 14 | if (this.$devicesService.deviceCount > 1) { 15 | this.$errors.failWithoutHelp("More than one device found. Specify device explicitly with --device option. To discover device ID, use $%s device command.", this.$staticConfig.CLIENT_NAME.toLowerCase()); 16 | } 17 | 18 | await this.$devicesService.execute(async (device: Mobile.IDevice) => await device.applicationManager.startApplication({ appId: args[0], projectName: args[1] })); 19 | } 20 | } 21 | 22 | $injector.registerCommand(["device|run", "devices|run"], RunApplicationOnDeviceCommand); 23 | -------------------------------------------------------------------------------- /appbuilder/project/cordova-project-capabilities.ts: -------------------------------------------------------------------------------- 1 | export class CordovaProjectCapabilities implements Project.ICapabilities { 2 | public get build(): boolean { 3 | return true; 4 | } 5 | 6 | public get buildCompanion(): boolean { 7 | return true; 8 | } 9 | 10 | public get deploy(): boolean { 11 | return true; 12 | } 13 | 14 | public get simulate(): boolean { 15 | return true; 16 | } 17 | 18 | public get livesync(): boolean { 19 | return true; 20 | } 21 | 22 | public get livesyncCompanion(): boolean { 23 | return true; 24 | } 25 | 26 | public get updateKendo(): boolean { 27 | return true; 28 | } 29 | 30 | public get emulate(): boolean { 31 | return true; 32 | } 33 | 34 | public get publish(): boolean { 35 | return false; 36 | } 37 | 38 | public get uploadToAppstore(): boolean { 39 | return true; 40 | } 41 | 42 | public get canChangeFrameworkVersion(): boolean { 43 | return true; 44 | } 45 | 46 | public get imageGeneration(): boolean { 47 | return true; 48 | } 49 | 50 | public get wp8Supported(): boolean { 51 | return true; 52 | } 53 | } 54 | $injector.register("cordovaProjectCapabilities", CordovaProjectCapabilities); 55 | -------------------------------------------------------------------------------- /commands/help.ts: -------------------------------------------------------------------------------- 1 | export class HelpCommand implements ICommand { 2 | constructor(private $injector: IInjector, 3 | private $helpService: IHelpService, 4 | private $options: ICommonOptions) { } 5 | 6 | public enableHooks = false; 7 | public async canExecute(args: string[]): Promise { 8 | return true; 9 | } 10 | 11 | public allowedParameters: ICommandParameter[] = []; 12 | 13 | public async execute(args: string[]): Promise { 14 | let commandName = (args[0] || "").toLowerCase(); 15 | let commandArguments = _.tail(args); 16 | const hierarchicalCommand = this.$injector.buildHierarchicalCommand(args[0], commandArguments); 17 | if (hierarchicalCommand) { 18 | commandName = hierarchicalCommand.commandName; 19 | commandArguments = hierarchicalCommand.remainingArguments; 20 | } 21 | 22 | const commandData: ICommandData = { 23 | commandName, 24 | commandArguments 25 | }; 26 | 27 | if (this.$options.help) { 28 | await this.$helpService.showCommandLineHelp(commandData); 29 | } else { 30 | await this.$helpService.openHelpForCommandInBrowser(commandData); 31 | } 32 | } 33 | } 34 | 35 | $injector.registerCommand(["help", "/?"], HelpCommand); 36 | -------------------------------------------------------------------------------- /services/settings-service.ts: -------------------------------------------------------------------------------- 1 | import { exported } from "../decorators"; 2 | import * as path from "path"; 3 | import * as osenv from "osenv"; 4 | 5 | export class SettingsService implements ISettingsService { 6 | private _profileDir: string; 7 | 8 | constructor(private $staticConfig: Config.IStaticConfig, 9 | private $hostInfo: IHostInfo) { 10 | this._profileDir = this.getDefaultProfileDir(); 11 | } 12 | 13 | @exported("settingsService") 14 | public setSettings(settings: IConfigurationSettings): void { 15 | if (settings && settings.userAgentName) { 16 | this.$staticConfig.USER_AGENT_NAME = settings.userAgentName; 17 | } 18 | 19 | if (settings && settings.profileDir) { 20 | this._profileDir = path.resolve(settings.profileDir); 21 | } 22 | } 23 | 24 | public getProfileDir(): string { 25 | return this._profileDir; 26 | } 27 | 28 | private getDefaultProfileDir(): string { 29 | const defaultProfileDirLocation = this.$hostInfo.isWindows ? process.env.AppData : path.join(osenv.home(), ".local", "share"); 30 | return path.join(defaultProfileDirLocation, this.$staticConfig.PROFILE_DIR_NAME); 31 | } 32 | } 33 | 34 | $injector.register("settingsService", SettingsService); 35 | -------------------------------------------------------------------------------- /appbuilder/project/nativescript-project-capabilities.ts: -------------------------------------------------------------------------------- 1 | export class NativeScriptProjectCapabilities implements Project.ICapabilities { 2 | public get build(): boolean { 3 | return true; 4 | } 5 | 6 | public get buildCompanion(): boolean { 7 | return true; 8 | } 9 | 10 | public get deploy(): boolean { 11 | return true; 12 | } 13 | 14 | public get simulate(): boolean { 15 | return false; 16 | } 17 | 18 | public get livesync(): boolean { 19 | return true; 20 | } 21 | 22 | public get livesyncCompanion(): boolean { 23 | return true; 24 | } 25 | 26 | public get updateKendo(): boolean { 27 | return false; 28 | } 29 | 30 | public get emulate(): boolean { 31 | return true; 32 | } 33 | 34 | public get publish(): boolean { 35 | return false; 36 | } 37 | 38 | public get uploadToAppstore(): boolean { 39 | return true; 40 | } 41 | 42 | public get canChangeFrameworkVersion(): boolean { 43 | return true; 44 | } 45 | 46 | public get imageGeneration(): boolean { 47 | return true; 48 | } 49 | 50 | public get wp8Supported(): boolean { 51 | return false; 52 | } 53 | } 54 | $injector.register("nativeScriptProjectCapabilities", NativeScriptProjectCapabilities); 55 | -------------------------------------------------------------------------------- /commands/device/list-files.ts: -------------------------------------------------------------------------------- 1 | export class ListFilesCommand implements ICommand { 2 | constructor(private $devicesService: Mobile.IDevicesService, 3 | private $stringParameter: ICommandParameter, 4 | private $options: ICommonOptions, 5 | private $project: Project.IProjectBase, 6 | private $errors: IErrors) { } 7 | 8 | public allowedParameters: ICommandParameter[] = [this.$stringParameter, this.$stringParameter]; 9 | 10 | public async execute(args: string[]): Promise { 11 | await this.$devicesService.initialize({ deviceId: this.$options.device, skipInferPlatform: true }); 12 | const pathToList = args[0]; 13 | let appIdentifier = args[1]; 14 | 15 | if (!appIdentifier && !this.$project.projectData) { 16 | this.$errors.failWithoutHelp("Please enter application identifier or execute this command in project."); 17 | } 18 | 19 | appIdentifier = appIdentifier || this.$project.projectData.AppIdentifier; 20 | 21 | const action = (device: Mobile.IDevice) => device.fileSystem.listFiles(pathToList, appIdentifier); 22 | await this.$devicesService.execute(action); 23 | } 24 | } 25 | 26 | $injector.registerCommand(["device|list-files", "devices|list-files"], ListFilesCommand); 27 | -------------------------------------------------------------------------------- /definitions/minimatch.d.ts: -------------------------------------------------------------------------------- 1 | declare module minimatch { 2 | export interface Options { 3 | debug?: boolean; 4 | nobrace?: boolean; 5 | noglobstar?: boolean; 6 | dot?: boolean; 7 | noext?: boolean; 8 | nonull?: boolean; 9 | nocase?: boolean; 10 | matchBase?: boolean; 11 | nocomment?: boolean; 12 | nonegate?: boolean; 13 | flipNegate?: boolean; 14 | } 15 | 16 | export interface Minimatch { 17 | constructor(pattern: string, options: Options): Minimatch; 18 | pattern: string; 19 | options: Options; 20 | regexp: RegExp; 21 | set: any[][]; 22 | negate: boolean; 23 | comment: boolean; 24 | empty: boolean; 25 | makeRe(): RegExp; 26 | match(path: string): boolean; 27 | } 28 | 29 | export interface IMinimatch { 30 | (path: string, pattern: string, options?: Options): boolean; 31 | filter(pattern: string, options?: Options): (path: string) => boolean; 32 | match(fileList: string[], pattern: string, options?: Options): string[]; 33 | makeRe(pattern: string, options?: Options): RegExp; 34 | 35 | Minimatch: minimatch.Minimatch; 36 | } 37 | } 38 | 39 | declare var minimatch: minimatch.IMinimatch; 40 | 41 | declare module "minimatch" { 42 | export = minimatch; 43 | } 44 | -------------------------------------------------------------------------------- /definitions/log4js.d.ts: -------------------------------------------------------------------------------- 1 | declare module "log4js" { 2 | interface ILogger { 3 | fatal(formatStr: string, ...args: string[]): void; 4 | error(formatStr: string, ...args: string[]): void; 5 | warn(formatStr: string, ...args: string[]): void; 6 | info(formatStr: string, ...args: string[]): void; 7 | debug(formatStr: string, ...args: string[]): void; 8 | trace(formatStr: string, ...args: string[]): void; 9 | 10 | setLevel(level: string): void; 11 | level: any; 12 | } 13 | 14 | interface IConfiguration { 15 | appenders: IAppender[]; 16 | } 17 | 18 | interface IAppender { 19 | type: string; 20 | layout: ILayout; 21 | } 22 | 23 | interface ILayout { 24 | type: string; 25 | } 26 | 27 | function configure(conf: IConfiguration): void; 28 | function getLogger(categoryName?: string): ILogger; 29 | 30 | export class Level { 31 | isEqualTo(level: any): boolean; 32 | isLessThanOrEqualTo(level: any): boolean; 33 | isGreaterThanOrEqualTo(level: any): boolean; 34 | } 35 | 36 | export namespace levels { 37 | var ALL: Level; 38 | var TRACE: Level; 39 | var DEBUG: Level; 40 | var INFO: Level; 41 | var WARN: Level; 42 | var ERROR: Level; 43 | var FATAL: Level; 44 | var MARK: Level; 45 | var OFF: Level; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /services/ios-notification-service.ts: -------------------------------------------------------------------------------- 1 | import * as constants from "../constants"; 2 | 3 | export class IOSNotificationService implements IiOSNotificationService { 4 | constructor(private $iosDeviceOperations: IIOSDeviceOperations) { } 5 | 6 | public async awaitNotification(deviceIdentifier: string, socket: number, timeout: number): Promise { 7 | const notificationResponse = await this.$iosDeviceOperations.awaitNotificationResponse([{ 8 | deviceId: deviceIdentifier, 9 | socket: socket, 10 | timeout: timeout, 11 | responseCommandType: constants.IOS_RELAY_NOTIFICATION_COMMAND_TYPE, 12 | responsePropertyName: "Name" 13 | }]); 14 | 15 | return _.first(notificationResponse[deviceIdentifier]).response; 16 | } 17 | 18 | public async postNotification(deviceIdentifier: string, notification: string, commandType?: string): Promise { 19 | commandType = commandType || constants.IOS_POST_NOTIFICATION_COMMAND_TYPE; 20 | const response = await this.$iosDeviceOperations.postNotification([{ deviceId: deviceIdentifier, commandType: commandType, notificationName: notification }]); 21 | return +_.first(response[deviceIdentifier]).response; 22 | } 23 | } 24 | 25 | $injector.register("iOSNotificationService", IOSNotificationService); 26 | -------------------------------------------------------------------------------- /services/plugins/plugins-source-base.ts: -------------------------------------------------------------------------------- 1 | export abstract class PluginsSourceBase implements IPluginsSource { 2 | protected progressIndicatorMessage: string; 3 | protected projectDir: string; 4 | protected plugins: IBasicPluginInformation[]; 5 | 6 | private _isInitialized: boolean; 7 | 8 | constructor(protected $progressIndicator: IProgressIndicator, 9 | protected $logger: ILogger) { } 10 | 11 | public async initialize(projectDir: string, keywords: string[]): Promise { 12 | if (this._isInitialized) { 13 | return; 14 | } 15 | 16 | this.plugins = []; 17 | this.projectDir = projectDir; 18 | this._isInitialized = true; 19 | 20 | this.$logger.printInfoMessageOnSameLine(this.progressIndicatorMessage); 21 | await this.$progressIndicator.showProgressIndicator(this.initializeCore(projectDir, keywords), 2000); 22 | } 23 | 24 | public hasPlugins(): boolean { 25 | return !!(this.plugins && this.plugins.length); 26 | } 27 | 28 | public async getAllPlugins(): Promise { 29 | return this.plugins; 30 | } 31 | 32 | public abstract getPlugins(page: number, count: number): Promise; 33 | 34 | protected abstract initializeCore(projectDir: string, keywords: string[]): Promise; 35 | } 36 | -------------------------------------------------------------------------------- /appbuilder/appbuilder-bootstrap.ts: -------------------------------------------------------------------------------- 1 | require("../bootstrap"); 2 | $injector.require("projectConstants", "./appbuilder/project-constants"); 3 | $injector.require("projectFilesProvider", "./appbuilder/providers/project-files-provider"); 4 | $injector.require("pathFilteringService", "./appbuilder/services/path-filtering"); 5 | $injector.require("liveSyncServiceBase", "./services/livesync-service-base"); 6 | $injector.require("androidLiveSyncServiceLocator", "./appbuilder/services/livesync/android-livesync-service"); 7 | $injector.require("iosLiveSyncServiceLocator", "./appbuilder/services/livesync/ios-livesync-service"); 8 | $injector.require("deviceAppDataProvider", "./appbuilder/providers/device-app-data-provider"); 9 | $injector.requirePublic("companionAppsService", "./appbuilder/services/livesync/companion-apps-service"); 10 | $injector.require("nativeScriptProjectCapabilities", "./appbuilder/project/nativescript-project-capabilities"); 11 | $injector.require("cordovaProjectCapabilities", "./appbuilder/project/cordova-project-capabilities"); 12 | $injector.require("mobilePlatformsCapabilities", "./appbuilder/mobile-platforms-capabilities"); 13 | $injector.requirePublic("npmService", "./appbuilder/services/npm-service"); 14 | $injector.require("iOSLogFilter", "./mobile/ios/ios-log-filter"); 15 | -------------------------------------------------------------------------------- /services/process-service.ts: -------------------------------------------------------------------------------- 1 | export class ProcessService implements IProcessService { 2 | private static PROCESS_EXIT_SIGNALS = ["exit", "SIGINT", "SIGTERM"]; 3 | private _listeners: IListener[]; 4 | 5 | public get listenersCount(): number { 6 | return this._listeners.length; 7 | } 8 | 9 | constructor() { 10 | this._listeners = []; 11 | } 12 | 13 | public attachToProcessExitSignals(context: any, callback: () => void): void { 14 | const callbackToString = callback.toString(); 15 | 16 | if (this._listeners.length === 0) { 17 | _.each(ProcessService.PROCESS_EXIT_SIGNALS, (signal: string) => { 18 | process.on(signal, () => this.executeAllCallbacks.apply(this)); 19 | }); 20 | } 21 | 22 | if (!_.some(this._listeners, (listener: IListener) => context === listener.context && callbackToString === listener.callback.toString())) { 23 | this._listeners.push({ context, callback }); 24 | } 25 | } 26 | 27 | private executeAllCallbacks(): void { 28 | _.each(this._listeners, (listener: IListener) => { 29 | try { 30 | listener.callback.apply(listener.context); 31 | } catch (err) { 32 | // ignore the error and let other handlers to be called. 33 | } 34 | }); 35 | } 36 | } 37 | 38 | interface IListener { 39 | context: any; 40 | callback: () => void; 41 | } 42 | 43 | $injector.register("processService", ProcessService); 44 | -------------------------------------------------------------------------------- /appbuilder/providers/appbuilder-livesync-provider-base.ts: -------------------------------------------------------------------------------- 1 | export abstract class AppBuilderLiveSyncProviderBase implements ILiveSyncProvider { 2 | constructor(private $androidLiveSyncServiceLocator: { factory: Function }, 3 | private $iosLiveSyncServiceLocator: { factory: Function }) { } 4 | 5 | public get deviceSpecificLiveSyncServices(): IDictionary { 6 | return { 7 | android: (_device: Mobile.IDevice, $injector: IInjector): IDeviceLiveSyncService => { 8 | return $injector.resolve(this.$androidLiveSyncServiceLocator.factory, { _device: _device }); 9 | }, 10 | ios: (_device: Mobile.IDevice, $injector: IInjector): IDeviceLiveSyncService => { 11 | return $injector.resolve(this.$iosLiveSyncServiceLocator.factory, { _device: _device }); 12 | } 13 | }; 14 | } 15 | 16 | public abstract buildForDevice(device: Mobile.IDevice): Promise; 17 | 18 | public preparePlatformForSync(platform: string): Promise { 19 | return; 20 | } 21 | 22 | public canExecuteFastSync(filePath: string): boolean { 23 | return false; 24 | } 25 | 26 | public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { 27 | await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /services/dynamic-help-service.ts: -------------------------------------------------------------------------------- 1 | import * as os from "os"; 2 | import { formatListOfNames } from '../helpers'; 3 | 4 | export class DynamicHelpService implements IDynamicHelpService { 5 | constructor(private $dynamicHelpProvider: IDynamicHelpProvider) { } 6 | 7 | public isProjectType(...args: string[]): boolean { 8 | return this.$dynamicHelpProvider.isProjectType(args); 9 | } 10 | 11 | public isPlatform(...args: string[]): boolean { 12 | const platform = os.platform().toLowerCase(); 13 | return _.some(args, arg => arg.toLowerCase() === platform); 14 | } 15 | 16 | public getLocalVariables(options: { isHtml: boolean }): IDictionary { 17 | const isHtml = options.isHtml; 18 | // in html help we want to show all help. Only CONSOLE specific help(wrapped in if(isConsole) ) must be omitted 19 | const localVariables = this.$dynamicHelpProvider.getLocalVariables(options); 20 | localVariables["isLinux"] = isHtml || this.isPlatform("linux"); 21 | localVariables["isWindows"] = isHtml || this.isPlatform("win32"); 22 | localVariables["isMacOS"] = isHtml || this.isPlatform("darwin"); 23 | localVariables["isConsole"] = !isHtml; 24 | localVariables["isHtml"] = isHtml; 25 | localVariables["formatListOfNames"] = formatListOfNames; 26 | localVariables["isJekyll"] = false; 27 | 28 | return localVariables; 29 | } 30 | } 31 | $injector.register("dynamicHelpService", DynamicHelpService); 32 | -------------------------------------------------------------------------------- /definitions/config.d.ts: -------------------------------------------------------------------------------- 1 | declare module Config { 2 | interface IStaticConfig { 3 | PROJECT_FILE_NAME: string; 4 | CLIENT_NAME_KEY_IN_PROJECT_FILE?: string; 5 | CLIENT_NAME: string; 6 | USER_AGENT_NAME: string; 7 | CLIENT_NAME_ALIAS?: string; 8 | FULL_CLIENT_NAME?: string; 9 | ANALYTICS_API_KEY: string; 10 | ANALYTICS_EXCEPTIONS_API_KEY: string; 11 | ANALYTICS_INSTALLATION_ID_SETTING_NAME: string; 12 | TRACK_FEATURE_USAGE_SETTING_NAME: string; 13 | ERROR_REPORT_SETTING_NAME: string; 14 | SYS_REQUIREMENTS_LINK: string; 15 | version: string; 16 | getAdbFilePath(): Promise; 17 | disableAnalytics?: boolean; 18 | disableCommandHooks?: boolean; 19 | enableDeviceRunCommandOnWindows?: boolean; 20 | MAN_PAGES_DIR: string; 21 | HTML_PAGES_DIR: string; 22 | HTML_COMMON_HELPERS_DIR: string; 23 | HTML_CLI_HELPERS_DIR: string; 24 | pathToPackageJson: string; 25 | APP_RESOURCES_DIR_NAME: string; 26 | COMMAND_HELP_FILE_NAME: string; 27 | RESOURCE_DIR_PATH: string; 28 | PATH_TO_BOOTSTRAP: string; 29 | QR_SIZE: number; 30 | INSTALLATION_SUCCESS_MESSAGE?: string; 31 | PROFILE_DIR_NAME: string 32 | } 33 | 34 | interface IConfig { 35 | AB_SERVER?: string; 36 | AB_SERVER_PROTO?: string; 37 | DEBUG?: boolean; 38 | ON_PREM?: boolean; 39 | CI_LOGGER?: boolean; 40 | TYPESCRIPT_COMPILER_OPTIONS?: ITypeScriptCompilerOptions; 41 | DISABLE_HOOKS: boolean; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /codeGeneration/code-entity.ts: -------------------------------------------------------------------------------- 1 | export enum CodeEntityType { 2 | Line, 3 | Block 4 | } 5 | 6 | export class Line implements CodeGeneration.ILine { 7 | public content: string; 8 | 9 | constructor(content: string) { 10 | this.content = content; 11 | } 12 | 13 | public get codeEntityType() { 14 | return CodeEntityType.Line; 15 | } 16 | 17 | public static create(content: string): CodeGeneration.ILine { 18 | return new Line(content); 19 | } 20 | } 21 | $injector.register("swaggerLine", Line); 22 | 23 | export class Block implements CodeGeneration.IBlock { 24 | public opener: string; 25 | public codeEntities: CodeGeneration.ICodeEntity[]; 26 | public endingCharacter: string; 27 | 28 | constructor(opener?: string) { 29 | this.opener = opener; 30 | this.codeEntities = []; 31 | } 32 | 33 | public get codeEntityType() { 34 | return CodeEntityType.Block; 35 | } 36 | 37 | public addBlock(block: CodeGeneration.IBlock): void { 38 | this.codeEntities.push(block); 39 | } 40 | 41 | public addLine(line: CodeGeneration.ILine): void { 42 | this.codeEntities.push(line); 43 | } 44 | 45 | public addBlocks(blocks: CodeGeneration.IBlock[]): void { 46 | _.each(blocks, (block: CodeGeneration.IBlock) => this.addBlock(block)); 47 | } 48 | 49 | public writeLine(content: string): void { 50 | const line = Line.create(content); 51 | this.codeEntities.push(line); 52 | } 53 | } 54 | $injector.register("swaggerBlock", Block); 55 | -------------------------------------------------------------------------------- /commands/device/device-log-stream.ts: -------------------------------------------------------------------------------- 1 | export class OpenDeviceLogStreamCommand implements ICommand { 2 | private static NOT_SPECIFIED_DEVICE_ERROR_MESSAGE = "More than one device found. Specify device explicitly."; 3 | 4 | constructor(private $devicesService: Mobile.IDevicesService, 5 | private $errors: IErrors, 6 | private $commandsService: ICommandsService, 7 | private $options: ICommonOptions, 8 | private $deviceLogProvider: Mobile.IDeviceLogProvider, 9 | private $loggingLevels: Mobile.ILoggingLevels, 10 | $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider) { 11 | $iOSSimulatorLogProvider.setShouldDispose(false); 12 | } 13 | 14 | allowedParameters: ICommandParameter[] = []; 15 | 16 | public async execute(args: string[]): Promise { 17 | this.$deviceLogProvider.setLogLevel(this.$loggingLevels.full); 18 | 19 | await this.$devicesService.initialize({ deviceId: this.$options.device, skipInferPlatform: true }); 20 | 21 | if (this.$devicesService.deviceCount > 1) { 22 | await this.$commandsService.tryExecuteCommand("device", []); 23 | this.$errors.fail(OpenDeviceLogStreamCommand.NOT_SPECIFIED_DEVICE_ERROR_MESSAGE); 24 | } 25 | 26 | const action = (device: Mobile.IiOSDevice) => device.openDeviceLogStream({predicate: 'senderImagePath contains "NativeScript"'}); 27 | await this.$devicesService.execute(action); 28 | } 29 | } 30 | 31 | $injector.registerCommand(["device|log", "devices|log"], OpenDeviceLogStreamCommand); 32 | -------------------------------------------------------------------------------- /appbuilder/device-log-provider.ts: -------------------------------------------------------------------------------- 1 | import { DeviceLogProviderBase } from "../mobile/device-log-provider-base"; 2 | 3 | export class DeviceLogProvider extends DeviceLogProviderBase { 4 | constructor(protected $logFilter: Mobile.ILogFilter, 5 | $logger: ILogger, 6 | private $loggingLevels: Mobile.ILoggingLevels) { 7 | super($logFilter, $logger); 8 | } 9 | 10 | public logData(line: string, platform: string, deviceIdentifier: string): void { 11 | this.setDefaultLogLevelForDevice(deviceIdentifier); 12 | 13 | const loggingOptions = this.getDeviceLogOptionsForDevice(deviceIdentifier) || { logLevel: this.$loggingLevels.info }; 14 | const data = this.$logFilter.filterData(platform, line, loggingOptions); 15 | 16 | if (data) { 17 | this.emit('data', deviceIdentifier, data); 18 | } 19 | } 20 | 21 | public setLogLevel(logLevel: string, deviceIdentifier?: string): void { 22 | if (deviceIdentifier) { 23 | this.setDeviceLogOptionsProperty(deviceIdentifier, (deviceLogOptions: Mobile.IDeviceLogOptions) => deviceLogOptions.logLevel, logLevel.toUpperCase()); 24 | } else { 25 | this.$logFilter.loggingLevel = logLevel.toUpperCase(); 26 | 27 | _.keys(this.devicesLogOptions).forEach(deviceId => { 28 | this.devicesLogOptions[deviceId] = this.devicesLogOptions[deviceId] || {}; 29 | this.devicesLogOptions[deviceId].logLevel = this.$logFilter.loggingLevel; 30 | }); 31 | } 32 | } 33 | } 34 | 35 | $injector.register("deviceLogProvider", DeviceLogProvider); 36 | -------------------------------------------------------------------------------- /appbuilder/mobile/appbuilder-device-app-data-base.ts: -------------------------------------------------------------------------------- 1 | import * as querystring from "querystring"; 2 | import { DeviceAppDataBase } from "./../../mobile/device-app-data/device-app-data-base"; 3 | 4 | export abstract class AppBuilderDeviceAppDataBase extends DeviceAppDataBase implements ILiveSyncDeviceAppData { 5 | constructor(_appIdentifier: string, 6 | public device: Mobile.IDevice, 7 | public platform: string, 8 | private $deployHelper: IDeployHelper) { 9 | super(_appIdentifier); 10 | } 11 | 12 | public abstract getDeviceProjectRootPath(): Promise; 13 | 14 | public get liveSyncFormat(): string { 15 | return null; 16 | } 17 | 18 | public encodeLiveSyncHostUri(hostUri: string): string { 19 | return querystring.escape(hostUri); 20 | } 21 | 22 | public getLiveSyncNotSupportedError(): string { 23 | return `You can't LiveSync on device with id ${this.device.deviceInfo.identifier}! Deploy the app with LiveSync enabled and wait for the initial start up before LiveSyncing.`; 24 | } 25 | 26 | public async isLiveSyncSupported(): Promise { 27 | const isApplicationInstalled = await this.device.applicationManager.isApplicationInstalled(this.appIdentifier); 28 | 29 | if (!isApplicationInstalled) { 30 | await this.$deployHelper.deploy(this.platform.toString()); 31 | // Update cache of installed apps 32 | await this.device.applicationManager.checkForApplicationUpdates(); 33 | } 34 | 35 | return await this.device.applicationManager.isLiveSyncSupported(this.appIdentifier); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mobile/android/device-android-debug-bridge.ts: -------------------------------------------------------------------------------- 1 | import { AndroidDebugBridge } from "./android-debug-bridge"; 2 | 3 | interface IComposeCommandResult { 4 | command: string; 5 | args: string[]; 6 | } 7 | 8 | export class DeviceAndroidDebugBridge extends AndroidDebugBridge implements Mobile.IDeviceAndroidDebugBridge { 9 | constructor(private identifier: string, 10 | protected $childProcess: IChildProcess, 11 | protected $errors: IErrors, 12 | protected $logger: ILogger, 13 | protected $staticConfig: Config.IStaticConfig, 14 | protected $androidDebugBridgeResultHandler: Mobile.IAndroidDebugBridgeResultHandler) { 15 | super($childProcess, $errors, $logger, $staticConfig, $androidDebugBridgeResultHandler); 16 | } 17 | 18 | public async sendBroadcastToDevice(action: string, extras?: IStringDictionary): Promise { 19 | extras = extras || {}; 20 | const broadcastCommand = ["am", "broadcast", "-a", `${action}`]; 21 | _.each(extras, (value, key) => broadcastCommand.push("-e", key, value)); 22 | 23 | const result = await this.executeShellCommand(broadcastCommand); 24 | this.$logger.trace(`Broadcast result ${result} from ${broadcastCommand}`); 25 | 26 | const match = result.match(/Broadcast completed: result=(\d+)/); 27 | if (match) { 28 | return +match[1]; 29 | } 30 | 31 | this.$errors.failWithoutHelp("Unable to broadcast to android device:\n%s", result); 32 | } 33 | 34 | protected async composeCommand(params: string[]): Promise { 35 | return super.composeCommand(params, this.identifier); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /commands/generate-messages.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | export class GenerateMessages implements ICommand { 4 | private static MESSAGES_DEFINITIONS_FILE_NAME = "messages.interface.d.ts"; 5 | private static MESSAGES_IMPLEMENTATION_FILE_NAME = "messages.ts"; 6 | 7 | constructor(private $fs: IFileSystem, 8 | private $messageContractGenerator: IServiceContractGenerator, 9 | private $options: ICommonOptions) { 10 | } 11 | 12 | allowedParameters: ICommandParameter[] = []; 13 | 14 | async execute(args: string[]): Promise { 15 | const result = await this.$messageContractGenerator.generate(); 16 | const innerMessagesDirectory = path.join(__dirname, "../messages"); 17 | const outerMessagesDirectory = path.join(__dirname, "../.."); 18 | let interfaceFilePath: string; 19 | let implementationFilePath: string; 20 | 21 | if (this.$options.default) { 22 | interfaceFilePath = path.join(innerMessagesDirectory, GenerateMessages.MESSAGES_DEFINITIONS_FILE_NAME); 23 | implementationFilePath = path.join(innerMessagesDirectory, GenerateMessages.MESSAGES_IMPLEMENTATION_FILE_NAME); 24 | } else { 25 | interfaceFilePath = path.join(outerMessagesDirectory, GenerateMessages.MESSAGES_DEFINITIONS_FILE_NAME); 26 | implementationFilePath = path.join(outerMessagesDirectory, GenerateMessages.MESSAGES_IMPLEMENTATION_FILE_NAME); 27 | } 28 | 29 | this.$fs.writeFile(interfaceFilePath, result.interfaceFile); 30 | this.$fs.writeFile(implementationFilePath, result.implementationFile); 31 | } 32 | } 33 | $injector.registerCommand("dev-generate-messages", GenerateMessages); 34 | -------------------------------------------------------------------------------- /commands/post-install.ts: -------------------------------------------------------------------------------- 1 | export class PostInstallCommand implements ICommand { 2 | constructor(private $fs: IFileSystem, 3 | private $staticConfig: Config.IStaticConfig, 4 | private $commandsService: ICommandsService, 5 | private $helpService: IHelpService, 6 | private $settingsService: ISettingsService, 7 | private $analyticsService: IAnalyticsService, 8 | protected $logger: ILogger) { 9 | } 10 | 11 | public disableAnalytics = true; 12 | public allowedParameters: ICommandParameter[] = []; 13 | 14 | public async execute(args: string[]): Promise { 15 | if (process.platform !== "win32") { 16 | // when running under 'sudo' we create a working dir with wrong owner (root) and 17 | // it is no longer accessible for the user initiating the installation 18 | // patch the owner here 19 | if (process.env.SUDO_USER) { 20 | await this.$fs.setCurrentUserAsOwner(this.$settingsService.getProfileDir(), process.env.SUDO_USER); 21 | } 22 | } 23 | 24 | await this.$helpService.generateHtmlPages(); 25 | 26 | // Explicitly ask for confirmation of usage-reporting: 27 | await this.$analyticsService.checkConsent(); 28 | 29 | await this.$commandsService.tryExecuteCommand("autocomplete", []); 30 | 31 | if (this.$staticConfig.INSTALLATION_SUCCESS_MESSAGE) { 32 | // Make sure the success message is separated with at least one line from all other messages. 33 | this.$logger.out(); 34 | this.$logger.printMarkdown(this.$staticConfig.INSTALLATION_SUCCESS_MESSAGE); 35 | } 36 | } 37 | } 38 | $injector.registerCommand("dev-post-install", PostInstallCommand); 39 | -------------------------------------------------------------------------------- /mobile/local-to-device-path-data-factory.ts: -------------------------------------------------------------------------------- 1 | import * as helpers from "../helpers"; 2 | import * as path from "path"; 3 | 4 | class LocalToDevicePathData implements Mobile.ILocalToDevicePathData { 5 | private devicePath: string; 6 | private relativeToProjectBasePath: string; 7 | 8 | constructor(private filePath: string, 9 | private localProjectRootPath: string, 10 | private onDeviceFileName: string, 11 | public deviceProjectRootPath: string) { } 12 | 13 | public getLocalPath(): string { 14 | return this.filePath; 15 | } 16 | 17 | public getDevicePath(): string { 18 | if (!this.devicePath) { 19 | const devicePath = path.join(this.deviceProjectRootPath, path.dirname(this.getRelativeToProjectBasePath()), this.onDeviceFileName); 20 | this.devicePath = helpers.fromWindowsRelativePathToUnix(devicePath); 21 | } 22 | 23 | return this.devicePath; 24 | } 25 | 26 | public getRelativeToProjectBasePath(): string { 27 | if (!this.relativeToProjectBasePath) { 28 | this.relativeToProjectBasePath = path.relative(this.localProjectRootPath, this.filePath); 29 | } 30 | 31 | return this.relativeToProjectBasePath; 32 | } 33 | } 34 | 35 | export class LocalToDevicePathDataFactory implements Mobile.ILocalToDevicePathDataFactory { 36 | create(filePath: string, localProjectRootPath: string, onDeviceFileName: string, deviceProjectRootPath: string): Mobile.ILocalToDevicePathData { 37 | return new LocalToDevicePathData(filePath, localProjectRootPath, onDeviceFileName, deviceProjectRootPath); 38 | } 39 | } 40 | $injector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); 41 | -------------------------------------------------------------------------------- /services/plugins/npm-registry-plugins-source.ts: -------------------------------------------------------------------------------- 1 | import { PluginsSourceBase } from "./plugins-source-base"; 2 | 3 | export class NpmRegistryPluginsSource extends PluginsSourceBase implements IPluginsSource { 4 | constructor(protected $progressIndicator: IProgressIndicator, 5 | protected $logger: ILogger, 6 | private $npmService: INpmService) { 7 | super($progressIndicator, $logger); 8 | } 9 | 10 | protected get progressIndicatorMessage(): string { 11 | return "Searching for plugin in http://registry.npmjs.org."; 12 | } 13 | 14 | public async getPlugins(page: number, count: number): Promise { 15 | return page === 1 ? this.plugins : null; 16 | } 17 | 18 | protected async initializeCore(projectDir: string, keywords: string[]): Promise { 19 | const plugin = await this.getPluginFromNpmRegistry(keywords[0]); 20 | this.plugins = plugin ? [plugin] : null; 21 | } 22 | 23 | private prepareScopedPluginName(plugin: string): string { 24 | return plugin.replace("/", "%2F"); 25 | } 26 | 27 | private async getPluginFromNpmRegistry(plugin: string): Promise { 28 | const dependencyInfo = this.$npmService.getDependencyInformation(plugin); 29 | 30 | const pluginName = this.$npmService.isScopedDependency(plugin) ? this.prepareScopedPluginName(dependencyInfo.name) : dependencyInfo.name; 31 | 32 | const result = await this.$npmService.getPackageJsonFromNpmRegistry(pluginName, dependencyInfo.version); 33 | 34 | if (!result) { 35 | return null; 36 | } 37 | 38 | result.author = (result.author && result.author.name) || result.author; 39 | return result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": true, 5 | "eofline": true, 6 | "indent": [ 7 | true, 8 | "tabs" 9 | ], 10 | "interface-name": true, 11 | "jsdoc-format": true, 12 | "max-line-length": [ 13 | false, 14 | 140 15 | ], 16 | "prefer-const": true, 17 | "no-consecutive-blank-lines": true, 18 | "no-construct": true, 19 | "no-debugger": true, 20 | "no-duplicate-variable": true, 21 | "no-shadowed-variable": true, 22 | "no-empty": true, 23 | "no-eval": true, 24 | "no-switch-case-fall-through": true, 25 | "no-trailing-whitespace": true, 26 | "no-unused-expression": true, 27 | "no-use-before-declare": true, 28 | "no-var-keyword": true, 29 | "no-var-requires": false, 30 | "one-line": [ 31 | true, 32 | "check-catch", 33 | "check-finally", 34 | "check-else", 35 | "check-open-brace", 36 | "check-whitespace" 37 | ], 38 | "no-floating-promises": true, 39 | "quotemark": [ 40 | false, 41 | "double" 42 | ], 43 | "semicolon": true, 44 | "space-before-function-paren": false, 45 | "switch-default": false, 46 | "trailing-comma": [ 47 | false, 48 | { 49 | "multiline": "always", 50 | "singleline": "always" 51 | } 52 | ], 53 | "triple-equals": [ 54 | true, 55 | "allow-null-check" 56 | ], 57 | "typeof-compare": true, 58 | "use-isnan": true, 59 | "variable-name": [ 60 | true, 61 | "ban-keywords", 62 | "allow-leading-underscore" 63 | ], 64 | "whitespace": [ 65 | true, 66 | "check-branch", 67 | "check-decl", 68 | "check-operator", 69 | "check-module", 70 | "check-separator" 71 | ] 72 | } 73 | } -------------------------------------------------------------------------------- /appbuilder/proton-bootstrap.ts: -------------------------------------------------------------------------------- 1 | require("./appbuilder-bootstrap"); 2 | $injector.require("messages", "./messages/messages"); 3 | 4 | import { OptionsBase } from "../options"; 5 | $injector.require("staticConfig", "./appbuilder/proton-static-config"); 6 | $injector.register("config", {}); 7 | // Proton will track the features and exceptions, so no need of analyticsService here. 8 | $injector.register("analyiticsService", {}); 9 | $injector.register("options", $injector.resolve(OptionsBase, { options: {}, defaultProfileDir: "" })); 10 | $injector.requirePublicClass("deviceEmitter", "./appbuilder/device-emitter"); 11 | $injector.requirePublicClass("deviceLogProvider", "./appbuilder/device-log-provider"); 12 | import { installUncaughtExceptionListener } from "../errors"; 13 | installUncaughtExceptionListener(); 14 | 15 | $injector.register("emulatorSettingsService", { 16 | canStart(platform: string): boolean { 17 | return true; 18 | }, 19 | minVersion(): number { 20 | return 10; 21 | } 22 | }); 23 | 24 | $injector.require("logger", "./logger"); 25 | // When debugging uncomment the lines below 26 | // $injector.resolve("logger").setLevel("TRACE"); 27 | 28 | // Mock as it is used in LiveSync logic to deploy on devices. 29 | // When called from Proton we'll not deploy on device, just livesync. 30 | $injector.register("deployHelper", { 31 | deploy: (platform?: string) => Promise.resolve() 32 | }); 33 | 34 | $injector.require("liveSyncProvider", "./appbuilder/providers/livesync-provider"); 35 | $injector.requirePublic("liveSyncService", "./appbuilder/services/livesync/livesync-service"); 36 | $injector.require("project", "./appbuilder/project/project"); 37 | -------------------------------------------------------------------------------- /appbuilder/project-constants.ts: -------------------------------------------------------------------------------- 1 | export class ProjectConstants implements Project.IConstants { 2 | public PROJECT_FILE = ".abproject"; 3 | public PROJECT_IGNORE_FILE = ".abignore"; 4 | public DEBUG_CONFIGURATION_NAME = "debug"; 5 | public DEBUG_PROJECT_FILE_NAME = ".debug.abproject"; 6 | public RELEASE_CONFIGURATION_NAME = "release"; 7 | public RELEASE_PROJECT_FILE_NAME = ".release.abproject"; 8 | public CORE_PLUGINS_PROPERTY_NAME = "CorePlugins"; 9 | public CORDOVA_PLUGIN_VARIABLES_PROPERTY_NAME = "CordovaPluginVariables"; 10 | public APPIDENTIFIER_PROPERTY_NAME = "AppIdentifier"; 11 | public EXPERIMENTAL_TAG = "Experimental"; 12 | public NATIVESCRIPT_APP_DIR_NAME = "app"; 13 | public IMAGE_DEFINITIONS_FILE_NAME = 'image-definitions.json'; 14 | public PACKAGE_JSON_NAME = "package.json"; 15 | public ADDITIONAL_FILE_DISPOSITION = "AdditionalFile"; 16 | public BUILD_RESULT_DISPOSITION = "BuildResult"; 17 | public ADDITIONAL_FILES_DIRECTORY = ".ab"; 18 | public REFERENCES_FILE_NAME = "abreferences.d.ts"; 19 | public OLD_REFERENCES_FILE_NAME = ".abreferences.d.ts"; 20 | public ANDROID_PLATFORM_NAME = "Android"; 21 | public IOS_PLATFORM_NAME = "iOS"; 22 | public WP8_PLATFORM_NAME = "WP8"; 23 | public TSCONFIG_JSON_NAME = "tsconfig.json"; 24 | 25 | public APPBUILDER_PROJECT_PLATFORMS_NAMES: IDictionary = { 26 | android: this.ANDROID_PLATFORM_NAME, 27 | ios: this.IOS_PLATFORM_NAME, 28 | wp8: this.WP8_PLATFORM_NAME 29 | }; 30 | 31 | public IONIC_PROJECT_PLATFORMS_NAMES: IDictionary = { 32 | android: "android", 33 | ios: "ios", 34 | wp8: "wp8" 35 | }; 36 | } 37 | 38 | $injector.register("projectConstants", ProjectConstants); 39 | -------------------------------------------------------------------------------- /mobile/android/android-livesync-service.ts: -------------------------------------------------------------------------------- 1 | class LiveSyncCommands { 2 | public static DeployProjectCommand(liveSyncUrl: string): string { 3 | return `DeployProject ${liveSyncUrl} \r`; 4 | } 5 | 6 | public static ReloadStartViewCommand(): string { 7 | return "ReloadStartView \r"; 8 | } 9 | 10 | public static SyncFilesCommand(): string { 11 | return "SyncFiles \r"; 12 | } 13 | 14 | public static RefreshCurrentViewCommand(): string { 15 | return "RefreshCurrentView \r"; 16 | } 17 | 18 | public static DeleteFile(relativePath: string): string { 19 | return `DeleteFile "${relativePath}" \r`; 20 | } 21 | } 22 | 23 | export class AndroidLiveSyncService implements Mobile.IAndroidLiveSyncService { 24 | private static COMMANDS_FILE = "telerik.livesync.commands"; 25 | private static LIVESYNC_BROADCAST_NAME = "com.telerik.LiveSync"; 26 | 27 | constructor(protected device: Mobile.IAndroidDevice, 28 | protected $fs: IFileSystem, 29 | protected $mobileHelper: Mobile.IMobileHelper) { } 30 | 31 | public get liveSyncCommands(): any { 32 | return LiveSyncCommands; 33 | } 34 | 35 | public async livesync(appIdentifier: string, liveSyncRoot: string, commands: string[]): Promise { 36 | const commandsFileDevicePath = this.$mobileHelper.buildDevicePath(liveSyncRoot, AndroidLiveSyncService.COMMANDS_FILE); 37 | await this.createCommandsFileOnDevice(commandsFileDevicePath, commands); 38 | await this.device.adb.sendBroadcastToDevice(AndroidLiveSyncService.LIVESYNC_BROADCAST_NAME, { "app-id": appIdentifier }); 39 | } 40 | 41 | public createCommandsFileOnDevice(commandsFileDevicePath: string, commands: string[]): Promise { 42 | return this.device.fileSystem.createFileOnDevice(commandsFileDevicePath, commands.join("\n")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /services/livesync/sync-batch.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/Microsoft/TypeScript/blob/master/src/compiler/tsc.ts#L487-L489 2 | export const SYNC_WAIT_THRESHOLD = 250; //milliseconds 3 | 4 | export class SyncBatch { 5 | private timer: NodeJS.Timer = null; 6 | private syncQueue: string[] = []; 7 | private syncInProgress: boolean = false; 8 | 9 | constructor(private $logger: ILogger, 10 | private $projectFilesManager: IProjectFilesManager, 11 | private done: () => Promise) { } 12 | 13 | private get filesToSync(): string[] { 14 | const filteredFiles = _.remove(this.syncQueue, syncFile => this.$projectFilesManager.isFileExcluded(syncFile)); 15 | this.$logger.trace("Removed files from syncQueue: ", filteredFiles); 16 | return this.syncQueue; 17 | } 18 | 19 | public get syncPending(): boolean { 20 | return this.syncQueue.length > 0; 21 | } 22 | 23 | public async syncFiles(syncAction: (filesToSync: string[]) => Promise): Promise { 24 | if (this.filesToSync.length > 0) { 25 | await syncAction(this.filesToSync); 26 | this.reset(); 27 | } 28 | } 29 | 30 | public async addFile(file: string): Promise { 31 | if (this.timer) { 32 | clearTimeout(this.timer); 33 | this.timer = null; 34 | } 35 | 36 | this.syncQueue.push(file); 37 | 38 | if (!this.syncInProgress) { 39 | this.timer = setTimeout(async () => { 40 | if (this.syncQueue.length > 0) { 41 | this.$logger.trace("Syncing %s", this.syncQueue.join(", ")); 42 | try { 43 | this.syncInProgress = true; 44 | await this.done(); 45 | } finally { 46 | this.syncInProgress = false; 47 | } 48 | } 49 | this.timer = null; 50 | }, SYNC_WAIT_THRESHOLD); 51 | } 52 | } 53 | 54 | private reset(): void { 55 | this.syncQueue = []; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /codeGeneration/code-printer.ts: -------------------------------------------------------------------------------- 1 | import { EOL } from "os"; 2 | import { CodeEntityType } from "./code-entity"; 3 | 4 | export class CodePrinter { 5 | private static INDENT_CHAR = "\t"; 6 | private static NEW_LINE_CHAR = EOL; 7 | private static START_BLOCK_CHAR = " {"; 8 | private static END_BLOCK_CHAR = "}"; 9 | 10 | public composeBlock(block: CodeGeneration.IBlock, indentSize?: number): string { 11 | indentSize = indentSize === undefined ? 0 : indentSize; 12 | let content = this.getIndentation(indentSize); 13 | 14 | if (block.opener) { 15 | content += block.opener; 16 | content += CodePrinter.START_BLOCK_CHAR; 17 | content += CodePrinter.NEW_LINE_CHAR; 18 | } 19 | 20 | _.each(block.codeEntities, (codeEntity: CodeGeneration.ICodeEntity) => { 21 | if (codeEntity.codeEntityType === CodeEntityType.Line) { 22 | content += this.composeLine(codeEntity, indentSize + 1); 23 | } else if (codeEntity.codeEntityType === CodeEntityType.Block) { 24 | content += this.composeBlock(codeEntity, indentSize + 1); 25 | } 26 | }); 27 | 28 | if (block.opener) { 29 | content += this.getIndentation(indentSize); 30 | content += CodePrinter.END_BLOCK_CHAR; 31 | content += block.endingCharacter || ''; 32 | content += CodePrinter.NEW_LINE_CHAR; 33 | } 34 | 35 | return content; 36 | } 37 | 38 | private getIndentation(indentSize: number): string { 39 | return Array(indentSize).join(CodePrinter.INDENT_CHAR); 40 | } 41 | 42 | private composeLine(line: CodeGeneration.ILine, indentSize: number): string { 43 | let content = this.getIndentation(indentSize); 44 | content += line.content; 45 | content += CodePrinter.NEW_LINE_CHAR; 46 | 47 | return content; 48 | } 49 | } 50 | $injector.register("swaggerCodePrinter", CodePrinter); 51 | -------------------------------------------------------------------------------- /mobile/mobile-core/ios-device-discovery.ts: -------------------------------------------------------------------------------- 1 | import { DeviceDiscovery } from "./device-discovery"; 2 | import { IOSDevice } from "../ios/device/ios-device"; 3 | 4 | export class IOSDeviceDiscovery extends DeviceDiscovery { 5 | private _iTunesErrorMessage: string; 6 | 7 | constructor(private $injector: IInjector, 8 | private $logger: ILogger, 9 | private $iTunesValidator: Mobile.IiTunesValidator, 10 | private $mobileHelper: Mobile.IMobileHelper, 11 | private $iosDeviceOperations: IIOSDeviceOperations) { 12 | super(); 13 | } 14 | 15 | private validateiTunes(): boolean { 16 | if (!this._iTunesErrorMessage) { 17 | this._iTunesErrorMessage = this.$iTunesValidator.getError(); 18 | 19 | if (this._iTunesErrorMessage) { 20 | this.$logger.warn(this._iTunesErrorMessage); 21 | } 22 | } 23 | 24 | return !this._iTunesErrorMessage; 25 | } 26 | 27 | public async startLookingForDevices(options?: Mobile.IDeviceLookingOptions): Promise { 28 | this.$logger.trace("Options for ios-device-discovery", options); 29 | 30 | if (options && options.platform && (!this.$mobileHelper.isiOSPlatform(options.platform) || options.emulator)) { 31 | return; 32 | } 33 | 34 | if (this.validateiTunes()) { 35 | await this.$iosDeviceOperations.startLookingForDevices((deviceInfo: IOSDeviceLib.IDeviceActionInfo) => { 36 | this.createAndAddDevice(deviceInfo); 37 | }, (deviceInfo: IOSDeviceLib.IDeviceActionInfo) => { 38 | this.removeDevice(deviceInfo.deviceId); 39 | }, options); 40 | } 41 | } 42 | 43 | private createAndAddDevice(deviceActionInfo: IOSDeviceLib.IDeviceActionInfo): void { 44 | const device = this.$injector.resolve(IOSDevice, { deviceActionInfo: deviceActionInfo }); 45 | this.addDevice(device); 46 | } 47 | } 48 | 49 | $injector.register("iOSDeviceDiscovery", IOSDeviceDiscovery); 50 | -------------------------------------------------------------------------------- /appbuilder/services/livesync/android-livesync-service.ts: -------------------------------------------------------------------------------- 1 | import { AndroidLiveSyncService } from "../../../mobile/android/android-livesync-service"; 2 | import * as path from "path"; 3 | import * as helpers from "../../../helpers"; 4 | 5 | export class AppBuilderAndroidLiveSyncService extends AndroidLiveSyncService implements IDeviceLiveSyncService { 6 | constructor(_device: Mobile.IAndroidDevice, 7 | $fs: IFileSystem, 8 | $mobileHelper: Mobile.IMobileHelper, 9 | private $options: ICommonOptions) { 10 | super(_device, $fs, $mobileHelper); 11 | } 12 | 13 | public async refreshApplication(deviceAppData: Mobile.IDeviceAppData): Promise { 14 | const commands = [this.liveSyncCommands.SyncFilesCommand()]; 15 | if (this.$options.watch || this.$options.file) { 16 | commands.push(this.liveSyncCommands.RefreshCurrentViewCommand()); 17 | } else { 18 | commands.push(this.liveSyncCommands.ReloadStartViewCommand()); 19 | } 20 | 21 | await this.livesync(deviceAppData.appIdentifier, await deviceAppData.getDeviceProjectRootPath(), commands); 22 | } 23 | 24 | public async removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { 25 | if (localToDevicePaths && localToDevicePaths.length) { 26 | const deviceProjectRootPath = localToDevicePaths[0].deviceProjectRootPath; 27 | const commands = _.map(localToDevicePaths, ldp => { 28 | 29 | const relativePath = path.relative(deviceProjectRootPath, ldp.getDevicePath()), 30 | unixPath = helpers.fromWindowsRelativePathToUnix(relativePath); 31 | 32 | return this.liveSyncCommands.DeleteFile(unixPath); 33 | }); 34 | await this.livesync(appIdentifier, deviceProjectRootPath, commands); 35 | } 36 | } 37 | } 38 | $injector.register("androidLiveSyncServiceLocator", { factory: AppBuilderAndroidLiveSyncService }); 39 | -------------------------------------------------------------------------------- /mobile/log-filter.ts: -------------------------------------------------------------------------------- 1 | export class LogFilter implements Mobile.ILogFilter { 2 | private _loggingLevel: string = this.$loggingLevels.info; 3 | 4 | constructor(private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, 5 | private $injector: IInjector, 6 | private $loggingLevels: Mobile.ILoggingLevels) { } 7 | 8 | public get loggingLevel(): string { 9 | return this._loggingLevel; 10 | } 11 | 12 | public set loggingLevel(logLevel: string) { 13 | if (this.verifyLogLevel(logLevel)) { 14 | this._loggingLevel = logLevel; 15 | } 16 | } 17 | 18 | public filterData(platform: string, data: string, loggingOptions: Mobile.IDeviceLogOptions = {}): string { 19 | loggingOptions = loggingOptions || {}; 20 | const deviceLogFilter = this.getDeviceLogFilterInstance(platform); 21 | loggingOptions.logLevel = loggingOptions.logLevel || this.loggingLevel; 22 | if (deviceLogFilter) { 23 | return deviceLogFilter.filterData(data, loggingOptions); 24 | } 25 | 26 | // In case the platform is not valid, just return the data without filtering. 27 | return data; 28 | } 29 | 30 | private getDeviceLogFilterInstance(platform: string): Mobile.IPlatformLogFilter { 31 | if (platform) { 32 | if (platform.toLowerCase() === this.$devicePlatformsConstants.iOS.toLowerCase()) { 33 | return this.$injector.resolve("iOSLogFilter"); 34 | } else if (platform.toLowerCase() === this.$devicePlatformsConstants.Android.toLowerCase()) { 35 | return this.$injector.resolve("androidLogFilter"); 36 | } 37 | } 38 | 39 | return null; 40 | } 41 | 42 | private verifyLogLevel(logLevel: string): boolean { 43 | const upperCaseLogLevel = (logLevel || '').toUpperCase(); 44 | return upperCaseLogLevel === this.$loggingLevels.info.toUpperCase() || upperCaseLogLevel === this.$loggingLevels.full.toUpperCase(); 45 | } 46 | 47 | } 48 | $injector.register("logFilter", LogFilter); 49 | -------------------------------------------------------------------------------- /definitions/node-uuid.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for node-uuid.js 2 | // Project: https://github.com/broofa/node-uuid 3 | // Definitions by: Jeff May 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | /// 7 | /// 8 | 9 | /** 10 | * Definitions for use in node environment 11 | * 12 | * !! For browser enviroments, use node-uuid-global or node-uuid-cjs 13 | */ 14 | declare module __NodeUUID { 15 | /** 16 | * Overloads for node environment 17 | * We need to duplicate some declarations because 18 | * interface merging doesn't work with overloads 19 | */ 20 | interface UUID { 21 | v1(options?: UUIDOptions): string; 22 | v1(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; 23 | v1(options?: UUIDOptions, buffer?: Buffer, offset?: number): Buffer; 24 | 25 | v2(options?: UUIDOptions): string; 26 | v2(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; 27 | v2(options?: UUIDOptions, buffer?: Buffer, offset?: number): Buffer; 28 | 29 | v3(options?: UUIDOptions): string; 30 | v3(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; 31 | v3(options?: UUIDOptions, buffer?: Buffer, offset?: number): Buffer; 32 | 33 | v4(options?: UUIDOptions): string; 34 | v4(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; 35 | v4(options?: UUIDOptions, buffer?: Buffer, offset?: number): Buffer; 36 | 37 | parse(id: string, buffer?: number[], offset?: number): number[]; 38 | parse(id: string, buffer?: Buffer, offset?: number): Buffer; 39 | 40 | unparse(buffer: number[], offset?: number): string; 41 | unparse(buffer: Buffer, offset?: number): string; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /services/xcode-select-service.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { cache } from "../decorators"; 3 | 4 | export class XcodeSelectService implements IXcodeSelectService { 5 | constructor(private $childProcess: IChildProcess, 6 | private $errors: IErrors, 7 | private $hostInfo: IHostInfo, 8 | private $injector: IInjector) { 9 | } 10 | 11 | public async getDeveloperDirectoryPath(): Promise { 12 | if (!this.$hostInfo.isDarwin) { 13 | this.$errors.failWithoutHelp("xcode-select is only available on Mac OS X."); 14 | } 15 | 16 | const childProcess = await this.$childProcess.spawnFromEvent("xcode-select", ["-print-path"], "close", {}, { throwError: false }), 17 | result = childProcess.stdout.trim(); 18 | 19 | if (!result) { 20 | this.$errors.failWithoutHelp("Cannot find path to Xcode.app - make sure you've installed Xcode correctly."); 21 | } 22 | 23 | return result; 24 | } 25 | 26 | public async getContentsDirectoryPath(): Promise { 27 | return path.join(await this.getDeveloperDirectoryPath(), ".."); 28 | } 29 | 30 | @cache() 31 | public async getXcodeVersion(): Promise { 32 | const sysInfo = this.$injector.resolve("sysInfo"); 33 | const xcodeVer = await sysInfo.getXcodeVersion(); 34 | if (!xcodeVer) { 35 | this.$errors.failWithoutHelp("xcodebuild execution failed. Make sure that you have latest Xcode and tools installed."); 36 | } 37 | 38 | const xcodeVersionMatch = xcodeVer.match(/Xcode (.*)/), 39 | xcodeVersionGroup = xcodeVersionMatch && xcodeVersionMatch[1], 40 | xcodeVersionSplit = xcodeVersionGroup && xcodeVersionGroup.split("."); 41 | 42 | return { 43 | major: xcodeVersionSplit && xcodeVersionSplit[0], 44 | minor: xcodeVersionSplit && xcodeVersionSplit[1], 45 | patch: xcodeVersionSplit && xcodeVersionSplit[2] 46 | }; 47 | } 48 | } 49 | 50 | $injector.register("xcodeSelectService", XcodeSelectService); 51 | -------------------------------------------------------------------------------- /definitions/node-uuid-base.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for node-uuid.js 2 | // Project: https://github.com/broofa/node-uuid 3 | // Definitions by: Jeff May 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | /** Common definitions for all environments */ 7 | declare namespace __NodeUUID { 8 | interface UUIDOptions { 9 | 10 | /** 11 | * Node id as Array of 6 bytes (per 4.1.6). 12 | * Default: Randomly generated ID. See note 1. 13 | */ 14 | node?: any[]; 15 | 16 | /** 17 | * (Number between 0 - 0x3fff) RFC clock sequence. 18 | * Default: An internally maintained clockseq is used. 19 | */ 20 | clockseq?: number; 21 | 22 | /** 23 | * (Number | Date) Time in milliseconds since unix Epoch. 24 | * Default: The current time is used. 25 | */ 26 | msecs?: number|Date; 27 | 28 | /** 29 | * (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if msecs is unspecified. 30 | * Default: internal uuid counter is used, as per 4.2.1.2. 31 | */ 32 | nsecs?: number; 33 | } 34 | 35 | interface UUID { 36 | v1(options?: UUIDOptions): string; 37 | v1(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; 38 | 39 | v2(options?: UUIDOptions): string; 40 | v2(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; 41 | 42 | v3(options?: UUIDOptions): string; 43 | v3(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; 44 | 45 | v4(options?: UUIDOptions): string; 46 | v4(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; 47 | 48 | parse(id: string, buffer?: number[], offset?: number): number[]; 49 | 50 | unparse(buffer: number[], offset?: number): string; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /services/plugins/npm-plugins-service.ts: -------------------------------------------------------------------------------- 1 | import { NpmPluginsSource } from "./npm-plugins-source"; 2 | import { NpmRegistryPluginsSource } from "./npm-registry-plugins-source"; 3 | import { NpmjsPluginsSource } from "./npmjs-plugins-source"; 4 | 5 | export class NpmPluginsService implements INpmPluginsService { 6 | constructor(private $injector: IInjector) { } 7 | 8 | public async search(projectDir: string, keywords: string[], modifySearchQuery: (keywords: string[]) => string[]): Promise { 9 | const query = modifySearchQuery ? modifySearchQuery(keywords) : keywords; 10 | 11 | const pluginsSource = await this.searchCore(NpmjsPluginsSource, projectDir, keywords) || 12 | await this.searchCore(NpmRegistryPluginsSource, projectDir, keywords) || 13 | await this.preparePluginsSource(NpmPluginsSource, projectDir, query); 14 | 15 | return pluginsSource; 16 | } 17 | 18 | public async optimizedSearch(projectDir: string, keywords: string[], modifySearchQuery: (keywords: string[]) => string[]): Promise { 19 | return await this.searchCore(NpmRegistryPluginsSource, projectDir, keywords) || await this.search(projectDir, keywords, modifySearchQuery); 20 | } 21 | 22 | private async searchCore(pluginsSourceConstructor: Function, projectDir: string, keywords: string[]): Promise { 23 | const npmPluginsSource = await this.preparePluginsSource(pluginsSourceConstructor, projectDir, keywords); 24 | 25 | return npmPluginsSource.hasPlugins() ? npmPluginsSource : null; 26 | } 27 | 28 | private async preparePluginsSource(pluginsSourceConstructor: Function, projectDir: string, keywords: string[]): Promise { 29 | const pluginsSource: IPluginsSource = this.$injector.resolve(pluginsSourceConstructor, { projectDir, keywords }); 30 | await pluginsSource.initialize(projectDir, keywords); 31 | return pluginsSource; 32 | } 33 | } 34 | 35 | $injector.register("npmPluginsService", NpmPluginsService); 36 | -------------------------------------------------------------------------------- /services/proxy-service.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { EOL } from "os"; 3 | import { Proxy } from "../constants"; 4 | const proxyLib = require("proxy-lib"); 5 | 6 | export class ProxyService implements IProxyService { 7 | private proxyCacheFilePath: string; 8 | private credentialsKey: string; 9 | 10 | constructor( 11 | private $settingsService: ISettingsService, 12 | private $staticConfig: Config.IStaticConfig) { 13 | this.proxyCacheFilePath = path.join(this.$settingsService.getProfileDir(), Proxy.CACHE_FILE_NAME); 14 | this.credentialsKey = `${this.$staticConfig.CLIENT_NAME}_PROXY`; 15 | } 16 | 17 | public setCache(settings: IProxyLibSettings): Promise { 18 | settings.userSpecifiedSettingsFilePath = settings.userSpecifiedSettingsFilePath || this.proxyCacheFilePath; 19 | settings.credentialsKey = settings.credentialsKey || this.credentialsKey; 20 | return proxyLib.setProxySettings(settings); 21 | } 22 | 23 | public getCache(): Promise { 24 | const settings = { 25 | credentialsKey: this.credentialsKey, 26 | userSpecifiedSettingsFilePath: this.proxyCacheFilePath 27 | }; 28 | 29 | return proxyLib.getProxySettings(settings); 30 | } 31 | 32 | public clearCache(): Promise { 33 | const settings = { 34 | credentialsKey: this.credentialsKey, 35 | userSpecifiedSettingsFilePath: this.proxyCacheFilePath 36 | }; 37 | 38 | return proxyLib.clearProxySettings(settings); 39 | } 40 | 41 | public async getInfo(): Promise { 42 | let message = ""; 43 | const proxyCache: IProxySettings = await this.getCache(); 44 | if (proxyCache) { 45 | message = `Proxy Url: ${proxyCache.protocol}//${proxyCache.hostname}:${proxyCache.port}`; 46 | if (proxyCache.username) { 47 | message += `${EOL}Username: ${proxyCache.username}`; 48 | } 49 | 50 | message += `${EOL}Proxy is Enabled`; 51 | } else { 52 | message = "No proxy set"; 53 | } 54 | 55 | return message; 56 | } 57 | } 58 | 59 | $injector.register("proxyService", ProxyService); 60 | -------------------------------------------------------------------------------- /appbuilder/services/path-filtering.ts: -------------------------------------------------------------------------------- 1 | import minimatch = require("minimatch"); 2 | 3 | export class PathFilteringService implements IPathFilteringService { 4 | 5 | constructor(private $fs: IFileSystem) { } 6 | 7 | public getRulesFromFile(fullFilePath: string): string[] { 8 | const COMMENT_START = '#'; 9 | let rules: string[] = []; 10 | 11 | try { 12 | const fileContent = this.$fs.readText(fullFilePath); 13 | rules = _.reject(fileContent.split(/[\n\r]/), 14 | (line: string) => line.length === 0 || line[0] === COMMENT_START); 15 | 16 | } catch (e) { 17 | if (e.code !== "ENOENT") { // file not found 18 | throw e; 19 | } 20 | } 21 | 22 | return rules; 23 | } 24 | 25 | public filterIgnoredFiles(files: string[], rules: string[], rootDir: string): string[] { 26 | return _.reject(files, file => this.isFileExcluded(file, rules, rootDir)); 27 | } 28 | 29 | public isFileExcluded(file: string, rules: string[], rootDir: string): boolean { 30 | file = file.replace(rootDir, "").replace(new RegExp("^[\\\\|/]*"), ""); 31 | let fileMatched = true; 32 | _.each(rules, rule => { 33 | // minimatch treats starting '!' as pattern negation 34 | // but we want the pattern matched and then do something else with the file 35 | // therefore, we manually handle leading ! and \! and hide them from minimatch 36 | const shouldInclude = rule[0] === '!'; 37 | if (shouldInclude) { 38 | rule = rule.substr(1); 39 | const ruleMatched = minimatch(file, rule, { nocase: true, dot: true }); 40 | if (ruleMatched) { 41 | fileMatched = true; 42 | } 43 | } else { 44 | const options = { nocase: true, nonegate: false, dot: true }; 45 | if (rule[0] === '\\' && rule[1] === '!') { 46 | rule = rule.substr(1); 47 | options.nonegate = true; 48 | } 49 | const ruleMatched = minimatch(file, rule, options); 50 | fileMatched = fileMatched && !ruleMatched; 51 | } 52 | }); 53 | 54 | return !fileMatched; 55 | } 56 | } 57 | 58 | $injector.register("pathFilteringService", PathFilteringService); 59 | -------------------------------------------------------------------------------- /definitions/yok.d.ts: -------------------------------------------------------------------------------- 1 | interface IInjector extends IDisposable { 2 | require(name: string, file: string): void; 3 | require(names: string[], file: string): void; 4 | requirePublic(names: string | string[], file: string): void; 5 | requirePublicClass(names: string | string[], file: string): void; 6 | requireCommand(name: string, file: string): void; 7 | requireCommand(names: string[], file: string): void; 8 | /** 9 | * Resolves an implementation by constructor function. 10 | * The injector will create new instances for every call. 11 | */ 12 | resolve(ctor: Function, ctorArguments?: { [key: string]: any }): any; 13 | resolve(ctor: Function, ctorArguments?: { [key: string]: any }): T; 14 | /** 15 | * Resolves an implementation by name. 16 | * The injector will create only one instance per name and return the same instance on subsequent calls. 17 | */ 18 | resolve(name: string, ctorArguments?: IDictionary): any; 19 | resolve(name: string, ctorArguments?: IDictionary): T; 20 | 21 | resolveCommand(name: string): ICommand; 22 | register(name: string, resolver: any, shared?: boolean): void; 23 | registerCommand(name: string, resolver: any): void; 24 | registerCommand(names: string[], resolver: any): void; 25 | getRegisteredCommandsNames(includeDev: boolean): string[]; 26 | dynamicCallRegex: RegExp; 27 | dynamicCall(call: string, args?: any[]): Promise; 28 | isDefaultCommand(commandName: string): boolean; 29 | isValidHierarchicalCommand(commandName: string, commandArguments: string[]): Promise; 30 | getChildrenCommandsNames(commandName: string): string[]; 31 | buildHierarchicalCommand(parentCommandName: string, commandLineArguments: string[]): any; 32 | publicApi: any; 33 | 34 | /** 35 | * Defines if it's allowed to override already required module. 36 | * This can be used in order to allow redefinition of modules, for example $logger can be replaced by a plugin. 37 | * Default value is false. 38 | */ 39 | overrideAlreadyRequiredModule: boolean; 40 | } 41 | 42 | declare var $injector: IInjector; 43 | -------------------------------------------------------------------------------- /mobile/wp8/wp8-emulator-services.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | class Wp8EmulatorServices implements Mobile.IEmulatorPlatformService { 4 | private static WP8_LAUNCHER = "XapDeployCmd.exe"; 5 | private static WP8_LAUNCHER_PATH = "Microsoft SDKs\\Windows Phone\\v8.0\\Tools\\XAP Deployment"; 6 | 7 | private static get programFilesPath(): string { 8 | return (process.arch === "x64") ? process.env["PROGRAMFILES(X86)"] : process.env.ProgramFiles; 9 | } 10 | 11 | constructor(private $logger: ILogger, 12 | private $childProcess: IChildProcess) { } 13 | 14 | public async getEmulatorId(): Promise { 15 | return ""; 16 | } 17 | 18 | public async getRunningEmulator(image: string): Promise { 19 | return null; 20 | } 21 | 22 | public async getRunningEmulatorImageIdentifier(emulatorId: string): Promise { 23 | return null; 24 | } 25 | 26 | public async getRunningEmulatorIds(): Promise { 27 | return []; 28 | } 29 | 30 | public async startEmulator(): Promise { 31 | return null; 32 | } 33 | 34 | public async runApplicationOnEmulator(app: string, emulatorOptions?: Mobile.IRunApplicationOnEmulatorOptions): Promise { 35 | this.$logger.info("Starting Windows Phone Emulator"); 36 | const emulatorStarter = this.getPathToEmulatorStarter(); 37 | this.$childProcess.spawn(emulatorStarter, ["/installlaunch", app, "/targetdevice:xd"], { stdio: "ignore", detached: true }).unref(); 38 | } 39 | 40 | public async getEmulatorImages(): Promise { 41 | return { devices: [], errors: []}; 42 | } 43 | 44 | public async getRunningEmulators(): Promise { 45 | return []; 46 | } 47 | 48 | public async getRunningEmulatorName(): Promise { 49 | return ""; 50 | } 51 | 52 | private getPathToEmulatorStarter(): string { 53 | return path.join(Wp8EmulatorServices.programFilesPath, Wp8EmulatorServices.WP8_LAUNCHER_PATH, Wp8EmulatorServices.WP8_LAUNCHER); 54 | } 55 | } 56 | 57 | $injector.register("wp8EmulatorServices", Wp8EmulatorServices); 58 | -------------------------------------------------------------------------------- /services/lockfile.ts: -------------------------------------------------------------------------------- 1 | import * as lockfile from "lockfile"; 2 | import * as path from "path"; 3 | import { cache } from "../decorators"; 4 | 5 | export class LockFile implements ILockFile { 6 | 7 | @cache() 8 | private get defaultLockFilePath(): string { 9 | return path.join(this.$settingsService.getProfileDir(), "lockfile.lock"); 10 | } 11 | 12 | private get defaultLockParams(): lockfile.Options { 13 | // We'll retry 100 times and time between retries is 100ms, i.e. full wait in case we are unable to get lock will be 10 seconds. 14 | // In case lock is older than 3 minutes, consider it stale and try to get a new lock. 15 | const lockParams: lockfile.Options = { 16 | retryWait: 100, 17 | retries: 100, 18 | stale: 180 * 1000, 19 | }; 20 | 21 | return lockParams; 22 | } 23 | 24 | constructor(private $fs: IFileSystem, 25 | private $settingsService: ISettingsService) { 26 | } 27 | 28 | public lock(lockFilePath?: string, lockFileOpts?: lockfile.Options): Promise { 29 | const { filePath, fileOpts } = this.getLockFileSettings(lockFilePath, lockFileOpts); 30 | 31 | // Prevent ENOENT error when the dir, where lock should be created, does not exist. 32 | this.$fs.ensureDirectoryExists(path.dirname(filePath)); 33 | 34 | return new Promise((resolve, reject) => { 35 | lockfile.lock(filePath, fileOpts, (err: Error) => { 36 | err ? reject(err) : resolve(); 37 | }); 38 | }); 39 | } 40 | 41 | public unlock(lockFilePath?: string): void { 42 | const { filePath } = this.getLockFileSettings(lockFilePath); 43 | lockfile.unlockSync(filePath); 44 | } 45 | 46 | public check(lockFilePath?: string, lockFileOpts?: lockfile.Options): boolean { 47 | const { filePath, fileOpts } = this.getLockFileSettings(lockFilePath, lockFileOpts); 48 | 49 | return lockfile.checkSync(filePath, fileOpts); 50 | } 51 | 52 | private getLockFileSettings(filePath?: string, fileOpts?: lockfile.Options): { filePath: string, fileOpts: lockfile.Options } { 53 | filePath = filePath || this.defaultLockFilePath; 54 | fileOpts = fileOpts || this.defaultLockParams; 55 | 56 | return { 57 | filePath, 58 | fileOpts 59 | }; 60 | } 61 | } 62 | 63 | $injector.register("lockfile", LockFile); 64 | -------------------------------------------------------------------------------- /definitions/commands.d.ts: -------------------------------------------------------------------------------- 1 | 2 | interface ICommand extends ICommandOptions { 3 | execute(args: string[]): Promise; 4 | allowedParameters: ICommandParameter[]; 5 | 6 | isDisabled?: boolean; 7 | 8 | // Implement this method in cases when you want to have your own logic for validation. In case you do not implement it, 9 | // the command will be evaluated from CommandsService's canExecuteCommand method. 10 | // One possible case where you can use this method is when you have two commandParameters, neither of them is mandatory, 11 | // but at least one of them is required. Used in prop|add, prop|set, etc. commands as their logic is complicated and 12 | // default validation in CommandsService is not applicable. 13 | canExecute?(args: string[]): Promise; 14 | completionData?: string[]; 15 | dashedOptions?: IDictionary; 16 | isHierarchicalCommand?: boolean; 17 | 18 | /** 19 | * Describes the action that will be executed after the command succeeds. 20 | * @param {string[]} args Arguments passed to the command. 21 | * @returns {Promise} 22 | */ 23 | postCommandAction?(args: string[]): Promise; 24 | } 25 | 26 | interface ICanExecuteCommandOutput { 27 | canExecute: boolean; 28 | /** 29 | * In case when canExecute method returns false, the help of the command is printed. 30 | * In case when canExecute method returns false and suppressCommandHelp is true, the command's help will not be printed. 31 | */ 32 | suppressCommandHelp?: boolean; 33 | } 34 | 35 | interface ICanExecuteCommandOptions { 36 | validateOptions?: boolean; 37 | notConfiguredEnvOptions?: INotConfiguredEnvOptions; 38 | } 39 | 40 | interface INotConfiguredEnvOptions { 41 | hideSyncToPreviewAppOption?: boolean; 42 | hideCloudBuildOption?: boolean; 43 | } 44 | 45 | interface IDynamicCommand extends ICommand { } 46 | 47 | interface ISimilarCommand { 48 | name: string; 49 | rating: number; 50 | } 51 | 52 | interface ICommandArgument { } 53 | 54 | interface ICommandParameter { 55 | mandatory: boolean; 56 | errorMessage?: string 57 | validate(value: string, errorMessage?: string): Promise; 58 | } 59 | 60 | interface IStringParameterBuilder { 61 | createMandatoryParameter(errorMsg: string): ICommandParameter; 62 | } 63 | -------------------------------------------------------------------------------- /mobile/mobile-core/android-emulator-discovery.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import { EmulatorDiscoveryNames } from "../../constants"; 3 | 4 | export class AndroidEmulatorDiscovery extends EventEmitter implements Mobile.IDeviceDiscovery { 5 | private _emulators: IDictionary = {}; 6 | 7 | constructor(private $androidEmulatorServices: Mobile.IEmulatorPlatformService, 8 | private $mobileHelper: Mobile.IMobileHelper) { super(); } 9 | 10 | public async startLookingForDevices(options?: Mobile.IDeviceLookingOptions): Promise { 11 | if (options && options.platform && !this.$mobileHelper.isAndroidPlatform(options.platform)) { 12 | return; 13 | } 14 | 15 | const availableEmulatorsOutput = await this.$androidEmulatorServices.getEmulatorImages(); 16 | const currentEmulators = availableEmulatorsOutput.devices; 17 | const cachedEmulators = _.values(this._emulators); 18 | 19 | // Remove old emulators 20 | const lostEmulators = _(cachedEmulators) 21 | .reject(e => _.find(currentEmulators, emulator => emulator && e && emulator.imageIdentifier === e.imageIdentifier)) 22 | .value(); 23 | 24 | // Add new emulators 25 | const foundEmulators = _(currentEmulators) 26 | .reject(e => _.find(cachedEmulators, emulator => emulator && e && emulator.imageIdentifier === e.imageIdentifier)) 27 | .value(); 28 | 29 | if (lostEmulators.length) { 30 | this.raiseOnEmulatorImagesLost(lostEmulators); 31 | } 32 | 33 | if (foundEmulators.length) { 34 | this.raiseOnEmulatorImagesFound(foundEmulators); 35 | } 36 | } 37 | 38 | public getDevices(): Mobile.IDeviceInfo[] { 39 | return _.values(this._emulators); 40 | } 41 | 42 | private raiseOnEmulatorImagesFound(emulators: Mobile.IDeviceInfo[]) { 43 | _.forEach(emulators, emulator => { 44 | this._emulators[emulator.imageIdentifier] = emulator; 45 | this.emit(EmulatorDiscoveryNames.EMULATOR_IMAGE_FOUND, emulator); 46 | }); 47 | } 48 | 49 | private raiseOnEmulatorImagesLost(emulators: Mobile.IDeviceInfo[]) { 50 | _.forEach(emulators, emulator => { 51 | delete this._emulators[emulator.imageIdentifier]; 52 | this.emit(EmulatorDiscoveryNames.EMULATOR_IMAGE_LOST, emulator); 53 | }); 54 | } 55 | } 56 | $injector.register("androidEmulatorDiscovery", AndroidEmulatorDiscovery); 57 | -------------------------------------------------------------------------------- /test/unit-tests/mocks/decorators-invoke-before.ts: -------------------------------------------------------------------------------- 1 | import { invokeBefore } from "../../../decorators"; 2 | 3 | export class InvokeBeforeDecoratorsTest { 4 | public counter = 0; 5 | 6 | public isInvokeBeforeMethodCalled = false; 7 | public invokedBeforeArgument: any; 8 | public invokedBeforeCount = 0; 9 | 10 | public invokedBeforeMethod(arg1: any): any { 11 | this.invokedBeforeCount++; 12 | this.invokedBeforeArgument = arg1; 13 | this.isInvokeBeforeMethodCalled = true; 14 | } 15 | 16 | public async promisifiedInvokedBeforeMethod(arg1: any): Promise { 17 | this.invokedBeforeCount++; 18 | this.invokedBeforeArgument = arg1; 19 | this.isInvokeBeforeMethodCalled = true; 20 | } 21 | 22 | public invokedBeforeThrowingMethod(arg1: any): any { 23 | this.invokedBeforeCount++; 24 | this.invokedBeforeArgument = arg1; 25 | this.isInvokeBeforeMethodCalled = true; 26 | 27 | throw new Error(arg1); 28 | } 29 | 30 | public async promisifiedInvokedBeforeThrowingMethod(arg1: any): Promise { 31 | this.invokedBeforeCount++; 32 | this.invokedBeforeArgument = arg1; 33 | this.isInvokeBeforeMethodCalled = true; 34 | 35 | throw new Error(arg1); 36 | } 37 | 38 | @invokeBefore("invokedBeforeMethod") 39 | public async method(num: number): Promise { 40 | this.counter++; 41 | return num; 42 | } 43 | 44 | @invokeBefore("promisifiedInvokedBeforeMethod") 45 | public async methodPromisifiedInvokeBefore(num: number): Promise { 46 | this.counter++; 47 | return num; 48 | } 49 | 50 | @invokeBefore("invokedBeforeThrowingMethod") 51 | public async methodInvokeBeforeThrowing(num: number): Promise { 52 | this.counter++; 53 | return num; 54 | } 55 | 56 | @invokeBefore("promisifiedInvokedBeforeThrowingMethod") 57 | public async methodPromisifiedInvokeBeforeThrowing(num: number): Promise { 58 | this.counter++; 59 | return num; 60 | } 61 | 62 | @invokeBefore("invokedBeforeMethod", ["arg1"]) 63 | public async methodCallingInvokeBeforeWithArgs(num: number): Promise { 64 | this.counter++; 65 | return num; 66 | } 67 | 68 | @invokeBefore("promisifiedInvokedBeforeMethod", ["arg1"]) 69 | public async methodPromisifiedInvokeBeforeWithArgs(num: number): Promise { 70 | this.counter++; 71 | return num; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /services/project-files-provider-base.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as util from "util"; 3 | import { Configurations } from "../constants"; 4 | 5 | export abstract class ProjectFilesProviderBase implements IProjectFilesProvider { 6 | abstract isFileExcluded(filePath: string): boolean; 7 | abstract mapFilePath(filePath: string, platform: string, projectData: any, projectFilesConfig: IProjectFilesConfig): string; 8 | 9 | constructor(private $mobileHelper: Mobile.IMobileHelper, 10 | protected $options: ICommonOptions) { } 11 | 12 | public getPreparedFilePath(filePath: string, projectFilesConfig?: IProjectFilesConfig): string { 13 | const projectFileInfo = this.getProjectFileInfo(filePath, "", projectFilesConfig); 14 | return path.join(path.dirname(filePath), projectFileInfo.onDeviceFileName); 15 | } 16 | 17 | public getProjectFileInfo(filePath: string, platform: string, projectFilesConfig?: IProjectFilesConfig): IProjectFileInfo { 18 | if (!filePath) { 19 | return { 20 | filePath: filePath, 21 | onDeviceFileName: filePath, 22 | shouldIncludeFile: false 23 | }; 24 | } 25 | 26 | let parsed = this.parseFile(filePath, this.$mobileHelper.platformNames, platform || ""); 27 | const basicConfigurations = [Configurations.Debug.toLowerCase(), Configurations.Release.toLowerCase()]; 28 | if (!parsed) { 29 | 30 | const validValues = basicConfigurations.concat(projectFilesConfig && projectFilesConfig.additionalConfigurations || []), 31 | value = projectFilesConfig && projectFilesConfig.configuration || basicConfigurations[0]; 32 | parsed = this.parseFile(filePath, validValues, value); 33 | } 34 | 35 | return parsed || { 36 | filePath: filePath, 37 | onDeviceFileName: path.basename(filePath), 38 | shouldIncludeFile: true 39 | }; 40 | } 41 | 42 | private parseFile(filePath: string, validValues: string[], value: string): IProjectFileInfo { 43 | const regex = util.format("^(.+?)[.](%s)([.].+?)$", validValues.join("|")); 44 | const parsed = filePath.match(new RegExp(regex, "i")); 45 | if (parsed) { 46 | return { 47 | filePath: filePath, 48 | onDeviceFileName: path.basename(parsed[1] + parsed[3]), 49 | shouldIncludeFile: parsed[2].toLowerCase() === value.toLowerCase() 50 | }; 51 | } 52 | 53 | return null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /appbuilder/providers/project-files-provider.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { ProjectFilesProviderBase } from "../../services/project-files-provider-base"; 3 | 4 | export class ProjectFilesProvider extends ProjectFilesProviderBase { 5 | private static IGNORE_FILE = ".abignore"; 6 | private static INTERNAL_NONPROJECT_FILES = [".ab", ProjectFilesProvider.IGNORE_FILE, ".*" + ProjectFilesProvider.IGNORE_FILE, "**/*.ipa", "**/*.apk", "**/*.xap"]; 7 | private _projectDir: string = null; 8 | private get projectDir(): string { 9 | if (!this._projectDir) { 10 | const project = this.$injector.resolve("project"); 11 | this._projectDir = project.getProjectDir(); 12 | } 13 | 14 | return this._projectDir; 15 | } 16 | 17 | constructor(private $pathFilteringService: IPathFilteringService, 18 | private $projectConstants: Project.IConstants, 19 | private $injector: IInjector, 20 | $mobileHelper: Mobile.IMobileHelper, 21 | $options: ICommonOptions) { 22 | super($mobileHelper, $options); 23 | } 24 | 25 | public isFileExcluded(filePath: string): boolean { 26 | const exclusionList = ProjectFilesProvider.INTERNAL_NONPROJECT_FILES.concat(this.getIgnoreFilesRules()); 27 | return this.$pathFilteringService.isFileExcluded(filePath, exclusionList, this.projectDir); 28 | } 29 | 30 | public mapFilePath(filePath: string, platform: string): string { 31 | return filePath; 32 | } 33 | 34 | private ignoreFilesRules: string[] = null; 35 | private getIgnoreFilesRules(): string[] { 36 | if (!this.ignoreFilesRules) { 37 | this.ignoreFilesRules = _(this.ignoreFilesConfigurations) 38 | .map(configFile => this.$pathFilteringService.getRulesFromFile(path.join(this.projectDir, configFile))) 39 | .flatten() 40 | .value(); 41 | } 42 | return this.ignoreFilesRules; 43 | } 44 | 45 | private get ignoreFilesConfigurations(): string[] { 46 | const configurations: string[] = [ProjectFilesProvider.IGNORE_FILE]; 47 | // unless release is explicitly set, we use debug config 48 | const configFileName = "." + 49 | (this.$options.release ? this.$projectConstants.RELEASE_CONFIGURATION_NAME : this.$projectConstants.DEBUG_CONFIGURATION_NAME) + 50 | ProjectFilesProvider.IGNORE_FILE; 51 | configurations.push(configFileName); 52 | return configurations; 53 | } 54 | } 55 | $injector.register("projectFilesProvider", ProjectFilesProvider); 56 | -------------------------------------------------------------------------------- /mobile/device-log-provider-base.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import { getPropertyName } from "../helpers"; 3 | 4 | export abstract class DeviceLogProviderBase extends EventEmitter implements Mobile.IDeviceLogProvider { 5 | protected devicesLogOptions: IDictionary = {}; 6 | 7 | constructor(protected $logFilter: Mobile.ILogFilter, 8 | protected $logger: ILogger) { 9 | super(); 10 | } 11 | 12 | public abstract logData(lineText: string, platform: string, deviceIdentifier: string): void; 13 | 14 | public abstract setLogLevel(logLevel: string, deviceIdentifier?: string): void; 15 | 16 | public setApplicationPidForDevice(deviceIdentifier: string, pid: string): void { 17 | this.setDeviceLogOptionsProperty(deviceIdentifier, (deviceLogOptions: Mobile.IDeviceLogOptions) => deviceLogOptions.applicationPid, pid); 18 | } 19 | 20 | public setProjectNameForDevice(deviceIdentifier: string, projectName: string): void { 21 | this.setDeviceLogOptionsProperty(deviceIdentifier, (deviceLogOptions: Mobile.IDeviceLogOptions) => deviceLogOptions.projectName, projectName); 22 | } 23 | 24 | protected setDefaultLogLevelForDevice(deviceIdentifier: string): void { 25 | const logLevel = (this.devicesLogOptions[deviceIdentifier] && this.devicesLogOptions[deviceIdentifier].logLevel) || this.$logFilter.loggingLevel; 26 | this.setLogLevel(logLevel, deviceIdentifier); 27 | } 28 | 29 | protected getApplicationPidForDevice(deviceIdentifier: string): string { 30 | return this.devicesLogOptions[deviceIdentifier] && this.devicesLogOptions[deviceIdentifier].applicationPid; 31 | } 32 | 33 | protected getDeviceLogOptionsForDevice(deviceIdentifier: string): Mobile.IDeviceLogOptions { 34 | const loggingOptions = this.devicesLogOptions[deviceIdentifier]; 35 | if (!loggingOptions) { 36 | this.setDefaultLogLevelForDevice(deviceIdentifier); 37 | } 38 | 39 | return this.devicesLogOptions[deviceIdentifier]; 40 | } 41 | 42 | protected setDeviceLogOptionsProperty(deviceIdentifier: string, propNameFunction: Function, propertyValue: string): void { 43 | const propertyName = getPropertyName(propNameFunction); 44 | 45 | if (propertyName) { 46 | this.devicesLogOptions[deviceIdentifier] = this.devicesLogOptions[deviceIdentifier] || {}; 47 | this.devicesLogOptions[deviceIdentifier][propertyName] = propertyValue; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /services/plugins/print-plugins-service.ts: -------------------------------------------------------------------------------- 1 | import { createTable, isInteractive } from "../../helpers"; 2 | 3 | export class PrintPluginsService implements IPrintPluginsService { 4 | private static COUNT_OF_PLUGINS_TO_DISPLAY: number = 10; 5 | 6 | private _page: number; 7 | 8 | constructor(private $logger: ILogger, 9 | private $prompter: IPrompter) { 10 | this._page = 1; 11 | } 12 | 13 | public async printPlugins(pluginsSource: IPluginsSource, options: IPrintPluginsOptions): Promise { 14 | if (!pluginsSource.hasPlugins()) { 15 | this.$logger.warn("No plugins found."); 16 | return; 17 | } 18 | 19 | const count: number = options.count || PrintPluginsService.COUNT_OF_PLUGINS_TO_DISPLAY; 20 | 21 | if (!isInteractive() || options.showAllPlugins) { 22 | const allPlugins = await pluginsSource.getAllPlugins(); 23 | this.displayTableWithPlugins(allPlugins); 24 | return; 25 | } 26 | 27 | let pluginsToDisplay: IBasicPluginInformation[] = await pluginsSource.getPlugins(this._page++, count); 28 | let shouldDisplayMorePlugins = true; 29 | 30 | this.$logger.out("Available plugins:"); 31 | 32 | do { 33 | this.displayTableWithPlugins(pluginsToDisplay); 34 | 35 | if (pluginsToDisplay.length < count) { 36 | return; 37 | } 38 | 39 | shouldDisplayMorePlugins = await this.$prompter.confirm("Load more plugins?"); 40 | 41 | pluginsToDisplay = await pluginsSource.getPlugins(this._page++, count); 42 | 43 | if (!pluginsToDisplay || pluginsToDisplay.length < 1) { 44 | return; 45 | } 46 | } while (shouldDisplayMorePlugins); 47 | } 48 | 49 | private displayTableWithPlugins(plugins: IBasicPluginInformation[]): void { 50 | let data: string[][] = []; 51 | data = this.createTableCells(plugins); 52 | 53 | const table: any = this.createPluginsTable(data); 54 | 55 | this.$logger.out(table.toString()); 56 | } 57 | 58 | private createPluginsTable(data: string[][]): any { 59 | const headers: string[] = ["Plugin", "Version", "Author", "Description"]; 60 | 61 | const table: any = createTable(headers, data); 62 | 63 | return table; 64 | } 65 | 66 | private createTableCells(plugins: IBasicPluginInformation[]): string[][] { 67 | return _.map(plugins, (plugin: IBasicPluginInformation) => [plugin.name, plugin.version, plugin.author || "", plugin.description || ""]); 68 | } 69 | } 70 | 71 | $injector.register("printPluginsService", PrintPluginsService); 72 | -------------------------------------------------------------------------------- /mobile/ios/ios-device-product-name-mapper.ts: -------------------------------------------------------------------------------- 1 | class IosDeviceProductNameMapper implements Mobile.IiOSDeviceProductNameMapper { 2 | // http://support.hockeyapp.net/kb/client-integration-ios-mac-os-x/ios-device-types 3 | private map: IStringDictionary = { 4 | "iPhone1,1": "iPhone", 5 | "iPhone1,2": "iPhone 3G", 6 | "iPhone2,1": "iPhone 3GS", 7 | "iPhone3,1": "iPhone 4 (GSM)", 8 | "iPhone3,3": "iPhone 4 (CDMA)", 9 | "iPhone4,1": "iPhone 4S", 10 | "iPhone5,1": "iPhone 5 (A1428)", 11 | "iPhone5,2": "iPhone 5 (A1429)", 12 | "iPhone5,3": "iPhone 5c (A1456/A1532)", 13 | "iPhone5,4": "iPhone 5c (A1507/A1516/A1529)", 14 | "iPhone6,1": "iPhone 5s (A1433/A1453)", 15 | "iPhone6,2": "iPhone 5s (A1457/A1518/A1530)", 16 | "iPhone7,1": "iPhone 6 Plus", 17 | "iPhone7,2": "iPhone 6", 18 | "iPhone8,1": "iPhone 6s", 19 | "iPhone8,2": "iPhone 6s Plus", 20 | "iPad1,1": "iPad", 21 | "iPad2,1": "iPad 2 (Wi-Fi)", 22 | "iPad2,2": "iPad 2 (GSM)", 23 | "iPad2,3": "iPad 2 (CDMA)", 24 | "iPad2,4": "iPad 2 (Wi-Fi, revised)", 25 | "iPad2,5": "iPad mini (Wi-Fi)", 26 | "iPad2,6": "iPad mini (A1454)", 27 | "iPad2,7": "iPad mini (A1455)", 28 | "iPad3,1": "iPad (3rd gen, Wi-Fi)", 29 | "iPad3,2": "iPad (3rd gen, Wi-Fi+LTE Verizon)", 30 | "iPad3,3": "iPad (3rd gen, Wi-Fi+LTE AT&T)", 31 | "iPad3,4": "iPad (4th gen, Wi-Fi)", 32 | "iPad3,5": "iPad (4th gen, A1459)", 33 | "iPad3,6": "iPad (4th gen, A1460)", 34 | "iPad4,1": "iPad Air (Wi-Fi)", 35 | "iPad4,2": "iPad Air (Wi-Fi+LTE)", 36 | "iPad4,3": "iPad Air (Rev)", 37 | "iPad4,4": "iPad mini 2 (Wi-Fi)", 38 | "iPad4,5": "iPad mini 2 (Wi-Fi+LTE)", 39 | "iPad4,6": "iPad mini 2 (Rev)", 40 | "iPad4,7": "iPad mini 3 (Wi-Fi)", 41 | "iPad4,8": "iPad mini 3 (A1600)", 42 | "iPad4,9": "iPad mini 3 (A1601)", 43 | "iPad5,1": "iPad mini 4 (Wi-Fi)", 44 | "iPad5,2": "iPad mini 4 (Wi-Fi+LTE)", 45 | "iPad5,3": "iPad Air 2 (Wi-Fi)", 46 | "iPad5,4": "iPad Air 2 (Wi-Fi+LTE)", 47 | "iPad6,7": "iPad Pro (Wi-Fi)", 48 | "iPad6,8": "iPad Pro (Wi-Fi+LTE)", 49 | "iPod1,1": "iPod touch", 50 | "iPod2,1": "iPod touch (2nd gen)", 51 | "iPod3,1": "iPod touch (3rd gen)", 52 | "iPod4,1": "iPod touch (4th gen)", 53 | "iPod5,1": "iPod touch (5th gen)", 54 | "iPod7,1": "iPod touch (6th gen)" 55 | }; 56 | 57 | public resolveProductName(deviceType: string): string { 58 | return this.map[deviceType]; 59 | } 60 | } 61 | $injector.register("iOSDeviceProductNameMapper", IosDeviceProductNameMapper); 62 | -------------------------------------------------------------------------------- /mobile/ios/simulator/ios-simulator-file-system.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as shelljs from "shelljs"; 3 | 4 | export class IOSSimulatorFileSystem implements Mobile.IDeviceFileSystem { 5 | constructor(private iosSim: any, 6 | private $fs: IFileSystem, 7 | private $logger: ILogger) { } 8 | 9 | public async listFiles(devicePath: string): Promise { 10 | return this.iosSim.listFiles(devicePath); 11 | } 12 | 13 | public async getFile(deviceFilePath: string, appIdentifier: string, outputFilePath?: string): Promise { 14 | if (outputFilePath) { 15 | shelljs.cp("-f", deviceFilePath, outputFilePath); 16 | } 17 | } 18 | 19 | public async putFile(localFilePath: string, deviceFilePath: string, appIdentifier: string): Promise { 20 | shelljs.cp("-f", localFilePath, deviceFilePath); 21 | } 22 | 23 | public async deleteFile(deviceFilePath: string, appIdentifier: string): Promise { 24 | shelljs.rm("-rf", deviceFilePath); 25 | } 26 | 27 | public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { 28 | await Promise.all( 29 | _.map(localToDevicePaths, localToDevicePathData => this.transferFile(localToDevicePathData.getLocalPath(), localToDevicePathData.getDevicePath()) 30 | )); 31 | return localToDevicePaths; 32 | } 33 | 34 | public async transferDirectory(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { 35 | const destinationPath = await deviceAppData.getDeviceProjectRootPath(); 36 | this.$logger.trace(`Transferring from ${projectFilesPath} to ${destinationPath}`); 37 | const sourcePath = path.join(projectFilesPath, "*"); 38 | shelljs.cp("-Rf", sourcePath, destinationPath); 39 | return localToDevicePaths; 40 | } 41 | 42 | public async transferFile(localFilePath: string, deviceFilePath: string): Promise { 43 | this.$logger.trace(`Transferring from ${localFilePath} to ${deviceFilePath}`); 44 | if (this.$fs.getFsStats(localFilePath).isDirectory()) { 45 | this.$fs.ensureDirectoryExists(deviceFilePath); 46 | } else { 47 | this.$fs.ensureDirectoryExists(path.dirname(deviceFilePath)); 48 | shelljs.cp("-f", localFilePath, deviceFilePath); 49 | } 50 | } 51 | 52 | public updateHashesOnDevice(hashes: IStringDictionary, appIdentifier: string): Promise { return; } 53 | } 54 | -------------------------------------------------------------------------------- /mobile/android/android-log-filter.ts: -------------------------------------------------------------------------------- 1 | const os = require("os"); 2 | 3 | export class AndroidLogFilter implements Mobile.IPlatformLogFilter { 4 | 5 | //sample line is "I/Web Console( 4438): Received Event: deviceready at file:///storage/emulated/0/Icenium/com.telerik.TestApp/js/index.js:48" 6 | private static LINE_REGEX = /.\/(.+?)\s*\(\s*\d+?\): (.*)/; 7 | 8 | // sample line is "11-23 12:39:07.310 1584 1597 I art : Background sticky concurrent mark sweep GC freed 21966(1780KB) AllocSpace objects, 4(80KB) LOS objects, 77% free, 840KB/3MB, paused 4.018ms total 158.629ms" 9 | // or '12-28 10:45:08.020 3329 3329 W chromium: [WARNING:data_reduction_proxy_settings.cc(328)] SPDY proxy OFF at startup' 10 | private static API_LEVEL_23_LINE_REGEX = /.+?\s+?(?:[A-Z]\s+?)([A-Za-z \.]+?)\s*?\: (.*)/; 11 | 12 | constructor(private $loggingLevels: Mobile.ILoggingLevels) { } 13 | 14 | public filterData(data: string, loggingOptions: Mobile.IDeviceLogOptions = {}): string { 15 | const specifiedLogLevel = (loggingOptions.logLevel || '').toUpperCase(); 16 | if (specifiedLogLevel === this.$loggingLevels.info) { 17 | const log = this.getConsoleLogFromLine(data, loggingOptions.applicationPid); 18 | if (log) { 19 | if (log.tag) { 20 | return `${log.tag}: ${log.message}` + os.EOL; 21 | } else { 22 | return log.message + os.EOL; 23 | } 24 | } 25 | 26 | return null; 27 | } 28 | 29 | return data + os.EOL; 30 | } 31 | 32 | private getConsoleLogFromLine(lineText: string, pid: string): any { 33 | // filter log line if it does not belong to the current application process id 34 | if (pid && lineText.indexOf(pid) < 0) { 35 | return null; 36 | } 37 | 38 | const acceptedTags = ["chromium", "Web Console", "JS", "ActivityManager", "System.err"]; 39 | 40 | let consoleLogMessage: { tag?: string, message: string }; 41 | 42 | const match = lineText.match(AndroidLogFilter.LINE_REGEX) || lineText.match(AndroidLogFilter.API_LEVEL_23_LINE_REGEX); 43 | 44 | if (match && acceptedTags.indexOf(match[1].trim()) !== -1) { 45 | consoleLogMessage = { tag: match[1].trim(), message: match[2] }; 46 | } 47 | 48 | if (!consoleLogMessage) { 49 | const matchingTag = _.some(acceptedTags, (tag: string) => { return lineText.indexOf(tag) !== -1; }); 50 | consoleLogMessage = matchingTag ? { message: lineText } : null; 51 | } 52 | 53 | return consoleLogMessage; 54 | } 55 | } 56 | $injector.register("androidLogFilter", AndroidLogFilter); 57 | -------------------------------------------------------------------------------- /project-helper.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | export class ProjectHelper implements IProjectHelper { 4 | constructor(private $logger: ILogger, 5 | private $fs: IFileSystem, 6 | private $staticConfig: Config.IStaticConfig, 7 | private $errors: IErrors, 8 | private $options: ICommonOptions) { } 9 | 10 | private cachedProjectDir = ""; 11 | 12 | public get projectDir(): string { 13 | if (this.cachedProjectDir !== "") { 14 | return this.cachedProjectDir; 15 | } 16 | this.cachedProjectDir = null; 17 | 18 | let projectDir = path.resolve(this.$options.path || "."); 19 | while (true) { 20 | this.$logger.trace("Looking for project in '%s'", projectDir); 21 | const projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME); 22 | 23 | if (this.$fs.exists(projectFilePath) && this.isProjectFileCorrect(projectFilePath)) { 24 | this.$logger.debug("Project directory is '%s'.", projectDir); 25 | this.cachedProjectDir = projectDir; 26 | break; 27 | } 28 | 29 | const dir = path.dirname(projectDir); 30 | if (dir === projectDir) { 31 | this.$logger.debug("No project found at or above '%s'.", this.$options.path || path.resolve(".")); 32 | break; 33 | } 34 | projectDir = dir; 35 | } 36 | 37 | return this.cachedProjectDir; 38 | } 39 | 40 | public generateDefaultAppId(appName: string, baseAppId: string): string { 41 | let sanitizedName = this.sanitizeName(appName); 42 | if (sanitizedName) { 43 | if (/^\d+$/.test(sanitizedName)) { 44 | sanitizedName = "the" + sanitizedName; 45 | } 46 | } else { 47 | sanitizedName = "the"; 48 | } 49 | 50 | return `${baseAppId}.${sanitizedName}`; 51 | } 52 | 53 | public sanitizeName(appName: string): string { 54 | const sanitizedName = _.filter(appName.split(""), (c) => /[a-zA-Z0-9]/.test(c)).join(""); 55 | return sanitizedName; 56 | } 57 | 58 | private isProjectFileCorrect(projectFilePath: string): boolean { 59 | if (this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE) { 60 | try { 61 | const fileContent = this.$fs.readJson(projectFilePath); 62 | const clientSpecificData = fileContent[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]; 63 | return !!clientSpecificData; 64 | } catch (err) { 65 | this.$errors.failWithoutHelp("The project file is corrupted. Additional technical information: %s", err); 66 | } 67 | } 68 | 69 | return true; 70 | } 71 | } 72 | $injector.register("projectHelper", ProjectHelper); 73 | -------------------------------------------------------------------------------- /services/cancellation.ts: -------------------------------------------------------------------------------- 1 | const gaze = require("gaze"); 2 | import * as path from "path"; 3 | import * as os from "os"; 4 | 5 | const hostInfo: IHostInfo = $injector.resolve("hostInfo"); 6 | 7 | class CancellationService implements ICancellationService { 8 | private watches: IDictionary = {}; 9 | 10 | constructor(private $fs: IFileSystem, 11 | private $logger: ILogger) { 12 | this.$fs.createDirectory(CancellationService.killSwitchDir); 13 | this.$fs.chmod(CancellationService.killSwitchDir, "0777"); 14 | } 15 | 16 | public async begin(name: string): Promise { 17 | const triggerFile = CancellationService.makeKillSwitchFileName(name); 18 | 19 | if (!this.$fs.exists(triggerFile)) { 20 | this.$fs.writeFile(triggerFile, ""); 21 | 22 | if (!hostInfo.isWindows) { 23 | this.$fs.chmod(triggerFile, "0777"); 24 | } 25 | } 26 | 27 | this.$logger.trace("Starting watch on killswitch %s", triggerFile); 28 | 29 | const watcherInitialized = new Promise((resolve, reject) => { 30 | gaze(triggerFile, function (err: any, watcher: any) { 31 | this.on("deleted", (filePath: string) => process.exit()); 32 | 33 | if (err) { 34 | reject(err); 35 | } else { 36 | resolve(watcher); 37 | } 38 | }); 39 | }); 40 | 41 | const watcher = await watcherInitialized; 42 | 43 | if (watcher) { 44 | this.watches[name] = watcher; 45 | } 46 | } 47 | 48 | public end(name: string): void { 49 | const watcher = this.watches[name]; 50 | delete this.watches[name]; 51 | watcher.close(); 52 | } 53 | 54 | public dispose(): void { 55 | _(this.watches).keys().each(name => this.end(name)); 56 | } 57 | 58 | private static get killSwitchDir(): string { 59 | return path.join(os.tmpdir(), process.env.SUDO_USER || process.env.USER || process.env.USERNAME || '', "KillSwitches"); 60 | } 61 | 62 | private static makeKillSwitchFileName(name: string): string { 63 | return path.join(CancellationService.killSwitchDir, name); 64 | } 65 | } 66 | 67 | class CancellationServiceDummy implements ICancellationService { 68 | dispose(): void { 69 | /* intentionally left blank */ 70 | } 71 | 72 | async begin(name: string): Promise { 73 | return; 74 | } 75 | 76 | end(name: string): void { 77 | /* intentionally left blank */ 78 | } 79 | } 80 | 81 | if (hostInfo.isWindows) { 82 | $injector.register("cancellation", CancellationService); 83 | } else { 84 | $injector.register("cancellation", CancellationServiceDummy); 85 | } 86 | -------------------------------------------------------------------------------- /commands/analytics.ts: -------------------------------------------------------------------------------- 1 | export class AnalyticsCommandParameter implements ICommandParameter { 2 | constructor(private $errors: IErrors) { } 3 | mandatory = false; 4 | async validate(validationValue: string): Promise { 5 | const val = validationValue || ""; 6 | switch (val.toLowerCase()) { 7 | case "enable": 8 | case "disable": 9 | case "status": 10 | case "": 11 | return true; 12 | default: 13 | this.$errors.fail(`The value '${validationValue}' is not valid. Valid values are 'enable', 'disable' and 'status'.`); 14 | } 15 | } 16 | } 17 | 18 | class AnalyticsCommand implements ICommand { 19 | constructor(protected $analyticsService: IAnalyticsService, 20 | private $logger: ILogger, 21 | private $errors: IErrors, 22 | private $options: ICommonOptions, 23 | private settingName: string, 24 | private humanReadableSettingName: string) { } 25 | 26 | public allowedParameters = [new AnalyticsCommandParameter(this.$errors)]; 27 | public disableAnalytics = true; 28 | 29 | public async execute(args: string[]): Promise { 30 | const arg = args[0] || ""; 31 | switch (arg.toLowerCase()) { 32 | case "enable": 33 | await this.$analyticsService.setStatus(this.settingName, true); 34 | await this.$analyticsService.track(this.settingName, "enabled"); 35 | this.$logger.info(`${this.humanReadableSettingName} is now enabled.`); 36 | break; 37 | case "disable": 38 | await this.$analyticsService.track(this.settingName, "disabled"); 39 | await this.$analyticsService.setStatus(this.settingName, false); 40 | this.$logger.info(`${this.humanReadableSettingName} is now disabled.`); 41 | break; 42 | case "status": 43 | case "": 44 | this.$logger.out(await this.$analyticsService.getStatusMessage(this.settingName, this.$options.json, this.humanReadableSettingName)); 45 | break; 46 | } 47 | } 48 | } 49 | 50 | export class UsageReportingCommand extends AnalyticsCommand { 51 | constructor(protected $analyticsService: IAnalyticsService, 52 | $logger: ILogger, 53 | $errors: IErrors, 54 | $options: ICommonOptions, 55 | $staticConfig: Config.IStaticConfig) { 56 | super($analyticsService, $logger, $errors, $options, $staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, "Usage reporting"); 57 | } 58 | } 59 | $injector.registerCommand("usage-reporting", UsageReportingCommand); 60 | 61 | export class ErrorReportingCommand extends AnalyticsCommand { 62 | constructor(protected $analyticsService: IAnalyticsService, 63 | $logger: ILogger, 64 | $errors: IErrors, 65 | $options: ICommonOptions, 66 | $staticConfig: Config.IStaticConfig 67 | ) { 68 | super($analyticsService, $logger, $errors, $options, $staticConfig.ERROR_REPORT_SETTING_NAME, "Error reporting"); 69 | } 70 | } 71 | $injector.registerCommand("error-reporting", ErrorReportingCommand); 72 | -------------------------------------------------------------------------------- /test/definitions/mocha.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for mocha 1.9.0 2 | // Project: http://visionmedia.github.io/mocha/ 3 | // Definitions by: Kazi Manzur Rashid 4 | // DefinitelyTyped: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | interface Mocha { 7 | // Setup mocha with the given setting options. 8 | setup(options: MochaSetupOptions): Mocha; 9 | 10 | //Run tests and invoke `fn()` when complete. 11 | run(callback?: () => void): void; 12 | 13 | // Set reporter as function 14 | reporter(reporter: () => void): Mocha; 15 | 16 | // Set reporter, defaults to "dot" 17 | reporter(reporter: string): Mocha; 18 | 19 | // Enable growl support. 20 | growl(): Mocha 21 | } 22 | 23 | interface MochaSetupOptions { 24 | //milliseconds to wait before considering a test slow 25 | slow?: number; 26 | 27 | // timeout in milliseconds 28 | timeout?: number; 29 | 30 | // ui name "bdd", "tdd", "exports" etc 31 | ui?: string; 32 | 33 | //array of accepted globals 34 | globals?: any[]; 35 | 36 | // reporter instance (function or string), defaults to `mocha.reporters.Dot` 37 | reporter?: any; 38 | 39 | // bail on the first test failure 40 | bail?: Boolean; 41 | 42 | // ignore global leaks 43 | ignoreLeaks?: Boolean; 44 | 45 | // grep string or regexp to filter tests with 46 | grep?: any; 47 | } 48 | 49 | declare module mocha { 50 | interface Done { 51 | (error?: Error): void; 52 | } 53 | } 54 | 55 | declare var describe : { 56 | (description: string, spec: () => void): void; 57 | only(description: string, spec: () => void): void; 58 | skip(description: string, spec: () => void): void; 59 | timeout(ms: number): void; 60 | } 61 | 62 | declare var it: { 63 | (expectation: string, assertion?: () => void): void; 64 | (expectation: string, assertion?: (done: mocha.Done) => void): void; 65 | only(expectation: string, assertion?: () => void): void; 66 | only(expectation: string, assertion?: (done: mocha.Done) => void): void; 67 | skip(expectation: string, assertion?: () => void): void; 68 | skip(expectation: string, assertion?: (done: mocha.Done) => void): void; 69 | timeout(ms: number): void; 70 | }; 71 | 72 | declare function before(action: () => void): void; 73 | 74 | declare function before(action: (done: mocha.Done) => void): void; 75 | 76 | declare function after(action: () => void): void; 77 | 78 | declare function after(action: (done: mocha.Done) => void): void; 79 | 80 | declare function beforeEach(action: () => void): void; 81 | 82 | declare function beforeEach(action: (done: mocha.Done) => void): void; 83 | 84 | declare function afterEach(action: () => void): void; 85 | 86 | declare function afterEach(action: (done: mocha.Done) => void): void; 87 | 88 | -------------------------------------------------------------------------------- /appbuilder/services/livesync/companion-apps-service.ts: -------------------------------------------------------------------------------- 1 | import { exported } from "../../../decorators"; 2 | import { TARGET_FRAMEWORK_IDENTIFIERS } from "../../../constants"; 3 | 4 | const NS_COMPANION_APP_IDENTIFIER = "com.telerik.NativeScript"; 5 | const APPBUILDER_ANDROID_COMPANION_APP_IDENTIFIER = "com.telerik.AppBuilder"; 6 | const APPBUILDER_IOS_COMPANION_APP_IDENTIFIER = "com.telerik.Icenium"; 7 | const APPBUILDER_WP8_COMPANION_APP_IDENTIFIER = "{9155af5b-e7ed-486d-bc6b-35087fb59ecc}"; 8 | 9 | export class CompanionAppsService implements ICompanionAppsService { 10 | constructor(private $mobileHelper: Mobile.IMobileHelper, 11 | private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { } 12 | 13 | @exported("companionAppsService") 14 | public getCompanionAppIdentifier(framework: string, platform: string): string { 15 | const lowerCasedFramework = (framework || "").toLowerCase(); 16 | const lowerCasedPlatform = (platform || "").toLowerCase(); 17 | 18 | if (lowerCasedFramework === TARGET_FRAMEWORK_IDENTIFIERS.Cordova.toLowerCase()) { 19 | if (this.$mobileHelper.isAndroidPlatform(lowerCasedPlatform)) { 20 | return APPBUILDER_ANDROID_COMPANION_APP_IDENTIFIER; 21 | } else if (this.$mobileHelper.isiOSPlatform(lowerCasedPlatform)) { 22 | return APPBUILDER_IOS_COMPANION_APP_IDENTIFIER; 23 | } else if (this.$mobileHelper.isWP8Platform(lowerCasedPlatform)) { 24 | return APPBUILDER_WP8_COMPANION_APP_IDENTIFIER; 25 | } 26 | } else if (lowerCasedFramework === TARGET_FRAMEWORK_IDENTIFIERS.NativeScript.toLowerCase()) { 27 | if (!this.$mobileHelper.isWP8Platform(lowerCasedPlatform)) { 28 | return NS_COMPANION_APP_IDENTIFIER; 29 | } 30 | } 31 | 32 | return null; 33 | } 34 | 35 | @exported("companionAppsService") 36 | public getAllCompanionAppIdentifiers(): IDictionary { 37 | const platforms = [ 38 | this.$devicePlatformsConstants.Android, 39 | this.$devicePlatformsConstants.iOS, 40 | this.$devicePlatformsConstants.WP8 41 | ]; 42 | 43 | const frameworks = [ 44 | TARGET_FRAMEWORK_IDENTIFIERS.Cordova.toLowerCase(), 45 | TARGET_FRAMEWORK_IDENTIFIERS.NativeScript.toLowerCase() 46 | ]; 47 | 48 | const companionAppIdentifiers: IDictionary = {}; 49 | _.each(frameworks, framework => { 50 | const lowerCasedFramework = framework.toLowerCase(); 51 | companionAppIdentifiers[lowerCasedFramework] = companionAppIdentifiers[lowerCasedFramework] || {}; 52 | _.each(platforms, platform => { 53 | const lowerCasedPlatform = platform.toLowerCase(); 54 | companionAppIdentifiers[lowerCasedFramework][lowerCasedPlatform] = this.getCompanionAppIdentifier(lowerCasedFramework, lowerCasedPlatform); 55 | }); 56 | }); 57 | 58 | return companionAppIdentifiers; 59 | } 60 | } 61 | $injector.register("companionAppsService", CompanionAppsService); 62 | -------------------------------------------------------------------------------- /mobile/mobile-core/android-device-discovery.ts: -------------------------------------------------------------------------------- 1 | import { DeviceDiscovery } from "./device-discovery"; 2 | import { AndroidDevice } from "../android/android-device"; 3 | 4 | interface IAdbAndroidDeviceInfo { 5 | identifier: string; 6 | status: string; 7 | } 8 | 9 | export class AndroidDeviceDiscovery extends DeviceDiscovery implements Mobile.IAndroidDeviceDiscovery { 10 | private _devices: IAdbAndroidDeviceInfo[] = []; 11 | private isStarted: boolean; 12 | 13 | constructor(private $injector: IInjector, 14 | private $adb: Mobile.IAndroidDebugBridge, 15 | private $mobileHelper: Mobile.IMobileHelper) { 16 | super(); 17 | } 18 | 19 | private async createAndAddDevice(adbDeviceInfo: IAdbAndroidDeviceInfo): Promise { 20 | this._devices.push(adbDeviceInfo); 21 | const device: Mobile.IAndroidDevice = this.$injector.resolve(AndroidDevice, { identifier: adbDeviceInfo.identifier, status: adbDeviceInfo.status }); 22 | await device.init(); 23 | this.addDevice(device); 24 | } 25 | 26 | private deleteAndRemoveDevice(deviceIdentifier: string): void { 27 | _.remove(this._devices, d => d.identifier === deviceIdentifier); 28 | this.removeDevice(deviceIdentifier); 29 | } 30 | 31 | public async startLookingForDevices(options?: Mobile.IDeviceLookingOptions): Promise { 32 | if (options && options.platform && !this.$mobileHelper.isAndroidPlatform(options.platform)) { 33 | return; 34 | } 35 | await this.ensureAdbServerStarted(); 36 | await this.checkForDevices(); 37 | } 38 | 39 | private async checkForDevices(): Promise { 40 | const devices = await this.$adb.getDevices(); 41 | 42 | await this.checkCurrentData(devices); 43 | } 44 | 45 | private async checkCurrentData(result: string[]): Promise { 46 | const currentDevices: IAdbAndroidDeviceInfo[] = result.map((element: string) => { 47 | // http://developer.android.com/tools/help/adb.html#devicestatus 48 | const data = element.split('\t'); 49 | const identifier = data[0]; 50 | const status = data[1]; 51 | 52 | return { 53 | identifier: identifier, 54 | status: status 55 | }; 56 | }); 57 | 58 | _(this._devices) 59 | .reject(d => _.find(currentDevices, device => device.identifier === d.identifier && device.status === d.status)) 60 | .each(d => this.deleteAndRemoveDevice(d.identifier)); 61 | 62 | await Promise.all(_(currentDevices) 63 | .reject(d => _.find(this._devices, device => device.identifier === d.identifier && device.status === d.status)) 64 | .map(d => this.createAndAddDevice(d)).value()); 65 | } 66 | 67 | public async ensureAdbServerStarted(): Promise { 68 | if (!this.isStarted) { 69 | this.isStarted = true; 70 | 71 | try { 72 | return await this.$adb.executeCommand(["start-server"]); 73 | } catch (err) { 74 | this.isStarted = false; 75 | throw err; 76 | } 77 | } 78 | } 79 | } 80 | 81 | $injector.register("androidDeviceDiscovery", AndroidDeviceDiscovery); 82 | -------------------------------------------------------------------------------- /mobile/ios/simulator/ios-simulator-log-provider.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcess } from "child_process"; 2 | import { DEVICE_LOG_EVENT_NAME } from "../../../constants"; 3 | import { EventEmitter } from "events"; 4 | 5 | export class IOSSimulatorLogProvider extends EventEmitter implements Mobile.IiOSSimulatorLogProvider, IDisposable, IShouldDispose { 6 | public shouldDispose: boolean; 7 | private simulatorsLoggingEnabled: IDictionary = {}; 8 | private simulatorsLogProcess: IDictionary = {}; 9 | 10 | constructor(private $iOSSimResolver: Mobile.IiOSSimResolver, 11 | private $logger: ILogger, 12 | private $processService: IProcessService) { 13 | super(); 14 | this.shouldDispose = true; 15 | } 16 | 17 | public setShouldDispose(shouldDispose: boolean) { 18 | this.shouldDispose = shouldDispose; 19 | } 20 | 21 | public async startLogProcess(deviceId: string, options?: Mobile.IiOSLogStreamOptions): Promise { 22 | if (!this.simulatorsLoggingEnabled[deviceId]) { 23 | const deviceLogChildProcess: ChildProcess = await this.$iOSSimResolver.iOSSim.getDeviceLogProcess(deviceId, options ? options.predicate : null); 24 | 25 | const action = (data: NodeBuffer | string) => { 26 | const message = data.toString(); 27 | this.emit(DEVICE_LOG_EVENT_NAME, { deviceId, message, muted: (options || {}).muted }); 28 | }; 29 | 30 | if (deviceLogChildProcess) { 31 | deviceLogChildProcess.once("close", () => { 32 | this.simulatorsLoggingEnabled[deviceId] = false; 33 | }); 34 | 35 | deviceLogChildProcess.once("error", (err) => { 36 | this.$logger.trace(`Error is thrown for device with identifier ${deviceId}. More info: ${err.message}.`); 37 | this.simulatorsLoggingEnabled[deviceId] = false; 38 | }); 39 | } 40 | 41 | if (deviceLogChildProcess.stdout) { 42 | deviceLogChildProcess.stdout.on("data", action.bind(this)); 43 | } 44 | 45 | if (deviceLogChildProcess.stderr) { 46 | deviceLogChildProcess.stderr.on("data", action.bind(this)); 47 | } 48 | 49 | this.$processService.attachToProcessExitSignals(this, deviceLogChildProcess.kill); 50 | 51 | this.simulatorsLoggingEnabled[deviceId] = true; 52 | this.simulatorsLogProcess[deviceId] = deviceLogChildProcess; 53 | } 54 | } 55 | 56 | public async startNewMutedLogProcess(deviceId: string, options?: Mobile.IiOSLogStreamOptions): Promise { 57 | options = options || {}; 58 | options.muted = true; 59 | this.simulatorsLoggingEnabled[deviceId] = false; 60 | await this.startLogProcess(deviceId, options); 61 | this.simulatorsLoggingEnabled[deviceId] = false; 62 | } 63 | 64 | public dispose(signal?: any) { 65 | if (this.shouldDispose) { 66 | _.each(this.simulatorsLogProcess, (logProcess: ChildProcess, deviceId: string) => { 67 | if (logProcess) { 68 | logProcess.kill(signal); 69 | } 70 | }); 71 | } 72 | } 73 | } 74 | $injector.register("iOSSimulatorLogProvider", IOSSimulatorLogProvider); 75 | -------------------------------------------------------------------------------- /mobile/android/android-ini-file-parser.ts: -------------------------------------------------------------------------------- 1 | import { AndroidVirtualDevice } from '../../constants'; 2 | import * as iconv from "iconv-lite"; 3 | 4 | export class AndroidIniFileParser implements Mobile.IAndroidIniFileParser { 5 | constructor(private $fs: IFileSystem) { 6 | iconv.extendNodeEncodings(); 7 | } 8 | 9 | public parseIniFile(iniFilePath: string): Mobile.IAvdInfo { 10 | if (!this.$fs.exists(iniFilePath)) { 11 | return null; 12 | } 13 | 14 | // avd files can have different encoding, defined on the first line. 15 | // find which one it is (if any) and use it to correctly read the file contents 16 | const encoding = this.getAvdEncoding(iniFilePath); 17 | const contents = this.$fs.readText(iniFilePath, encoding).split("\n"); 18 | 19 | return _.reduce(contents, (result: Mobile.IAvdInfo, line: string) => { 20 | const parsedLine = line.split("="); 21 | const key = parsedLine[0]; 22 | switch (key) { 23 | case "target": 24 | result.target = parsedLine[1]; 25 | result.targetNum = this.readTargetNum(result.target); 26 | break; 27 | case "path": 28 | case "AvdId": 29 | result[_.lowerFirst(key)] = parsedLine[1]; 30 | break; 31 | case "hw.device.name": 32 | result.device = parsedLine[1]; 33 | break; 34 | case "avd.ini.displayname": 35 | result.displayName = parsedLine[1]; 36 | break; 37 | case "abi.type": 38 | case "skin.name": 39 | case "sdcard.size": 40 | result[key.split(".")[0]] = parsedLine[1]; 41 | break; 42 | } 43 | return result; 44 | }, Object.create(null)); 45 | } 46 | 47 | private getAvdEncoding(avdName: string): any { 48 | // avd files can have different encoding, defined on the first line. 49 | // find which one it is (if any) and use it to correctly read the file contents 50 | let encoding = "utf8"; 51 | let contents = this.$fs.readText(avdName, "ascii"); 52 | if (contents.length > 0) { 53 | contents = contents.split("\n", 1)[0]; 54 | if (contents.length > 0) { 55 | const matches = contents.match(AndroidVirtualDevice.ENCODING_MASK); 56 | if (matches) { 57 | encoding = matches[1]; 58 | } 59 | } 60 | } 61 | return encoding; 62 | } 63 | 64 | // Android L is not written as a number in the .ini files, and we need to convert it 65 | private readTargetNum(target: string): number { 66 | const platform = target.replace('android-', ''); 67 | let platformNumber = +platform; 68 | if (isNaN(platformNumber)) { 69 | // this may be a google image 70 | const googlePlatform = target.split(":")[2]; 71 | if (googlePlatform) { 72 | platformNumber = +googlePlatform; 73 | } else if (platform === "L") { // Android SDK 20 preview 74 | platformNumber = 20; 75 | } else if (platform === "MNC") { // Android M preview 76 | platformNumber = 22; 77 | } 78 | } 79 | return platformNumber; 80 | } 81 | } 82 | $injector.register("androidIniFileParser", AndroidIniFileParser); 83 | -------------------------------------------------------------------------------- /services/messages-service.ts: -------------------------------------------------------------------------------- 1 | import * as util from "util"; 2 | import * as path from "path"; 3 | 4 | export class MessagesService implements IMessagesService { 5 | private _pathsToMessageJsonFiles: string[] = null; 6 | private _messageJsonFilesContentsCache: any[] = null; 7 | 8 | private get pathToDefaultMessageJson(): string { 9 | return path.join(__dirname, "..", "resources", "messages", "errorMessages.json"); 10 | } 11 | 12 | private get messageJsonFilesContents(): any[] { 13 | if (!this._messageJsonFilesContentsCache || !this._messageJsonFilesContentsCache.length) { 14 | this.refreshMessageJsonContentsCache(); 15 | } 16 | 17 | return this._messageJsonFilesContentsCache; 18 | } 19 | 20 | constructor(private $fs: IFileSystem) { 21 | this._pathsToMessageJsonFiles = [this.pathToDefaultMessageJson]; 22 | } 23 | 24 | public get pathsToMessageJsonFiles(): string[] { 25 | if (!this._pathsToMessageJsonFiles) { 26 | throw new Error("No paths to message json files provided."); 27 | } 28 | 29 | return this._pathsToMessageJsonFiles; 30 | } 31 | 32 | public set pathsToMessageJsonFiles(pathsToMessageJsonFiles: string[]) { 33 | this._pathsToMessageJsonFiles = pathsToMessageJsonFiles.concat(this.pathToDefaultMessageJson); 34 | this.refreshMessageJsonContentsCache(); 35 | } 36 | 37 | public getMessage(id: string, ...args: string[]): string { 38 | const argsArray = args || []; 39 | 40 | const keys = id.split("."); 41 | let result = this.getFormatedMessage.apply(this, [id].concat(argsArray)); 42 | 43 | _.each(this.messageJsonFilesContents, jsonFileContents => { 44 | const messageValue = this.getMessageFromJsonRecursive(keys, jsonFileContents, 0); 45 | if (messageValue) { 46 | result = this.getFormatedMessage.apply(this, [messageValue].concat(argsArray)); 47 | return false; 48 | } 49 | }); 50 | 51 | return result; 52 | } 53 | 54 | private getMessageFromJsonRecursive(keys: string[], jsonContents: any, index: number): string { 55 | if (index >= keys.length) { 56 | return null; 57 | } 58 | 59 | const jsonValue = jsonContents[keys[index]]; 60 | if (!jsonValue) { 61 | return null; 62 | } 63 | 64 | if (typeof jsonValue === "string") { 65 | return jsonValue; 66 | } 67 | 68 | return this.getMessageFromJsonRecursive(keys, jsonValue, index + 1); 69 | } 70 | 71 | private refreshMessageJsonContentsCache(): void { 72 | this._messageJsonFilesContentsCache = []; 73 | _.each(this.pathsToMessageJsonFiles, path => { 74 | if (!this.$fs.exists(path)) { 75 | throw new Error("Message json file " + path + " does not exist."); 76 | } 77 | 78 | this._messageJsonFilesContentsCache.push(this.$fs.readJson(path)); 79 | }); 80 | } 81 | 82 | private getFormatedMessage(message: string, ...args: string[]): string { 83 | return ~message.indexOf("%") ? util.format.apply(null, [message].concat(args || [])) : message; 84 | } 85 | } 86 | 87 | $injector.register("messagesService", MessagesService); 88 | -------------------------------------------------------------------------------- /validators/iTunes-validator.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | export class ITunesValidator implements Mobile.IiTunesValidator { 4 | private static NOT_INSTALLED_iTUNES_ERROR_MESSAGE = "iTunes is not installed. Install it on your system and run this command again."; 5 | private static BITNESS_MISMATCH_ERROR_MESSAGE = "The bitness of Node.js and iTunes must match. Verify that both Node.js and iTunes are 32-bit or 64-bit and try again."; 6 | private static UNSUPPORTED_OS_ERROR_MESSAGE = "iTunes is not available for this operating system. You will not be able to work with connected iOS devices."; 7 | 8 | constructor(private $fs: IFileSystem, 9 | private $hostInfo: IHostInfo) { } 10 | 11 | public getError(): string { 12 | if (this.$hostInfo.isWindows) { 13 | let commonProgramFiles = ""; 14 | const isNode64 = process.arch === "x64"; 15 | 16 | if (isNode64) { //x64-windows 17 | commonProgramFiles = process.env.CommonProgramFiles; 18 | if (this.isiTunesInstalledOnWindows(process.env["CommonProgramFiles(x86)"]) && !this.isiTunesInstalledOnWindows(commonProgramFiles)) { 19 | return ITunesValidator.BITNESS_MISMATCH_ERROR_MESSAGE; 20 | } 21 | } else { 22 | if (this.$hostInfo.isWindows32) { // x86-node, x86-windows 23 | commonProgramFiles = process.env.CommonProgramFiles; 24 | } else { // x86-node, x64-windows 25 | // check for x64-iTunes 26 | commonProgramFiles = process.env["CommonProgramFiles(x86)"]; 27 | 28 | if (this.isiTunesInstalledOnWindows(process.env.CommonProgramFiles) && !this.isiTunesInstalledOnWindows(commonProgramFiles)) { 29 | return ITunesValidator.BITNESS_MISMATCH_ERROR_MESSAGE; 30 | } 31 | } 32 | } 33 | 34 | if (!this.isiTunesInstalledOnWindows(commonProgramFiles)) { 35 | return ITunesValidator.NOT_INSTALLED_iTUNES_ERROR_MESSAGE; 36 | } 37 | 38 | return null; 39 | } else if (this.$hostInfo.isDarwin) { 40 | const coreFoundationDir = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; 41 | const mobileDeviceDir = "/System/Library/PrivateFrameworks/MobileDevice.framework/MobileDevice"; 42 | 43 | if (!this.isiTunesInstalledCore(coreFoundationDir, mobileDeviceDir)) { 44 | return ITunesValidator.NOT_INSTALLED_iTUNES_ERROR_MESSAGE; 45 | } 46 | 47 | return null; 48 | } 49 | 50 | return ITunesValidator.UNSUPPORTED_OS_ERROR_MESSAGE; 51 | } 52 | 53 | private isiTunesInstalledOnWindows(commonProgramFiles: string): boolean { 54 | const coreFoundationDir = path.join(commonProgramFiles, "Apple", "Apple Application Support"); 55 | const mobileDeviceDir = path.join(commonProgramFiles, "Apple", "Mobile Device Support"); 56 | 57 | return this.isiTunesInstalledCore(coreFoundationDir, mobileDeviceDir); 58 | } 59 | 60 | private isiTunesInstalledCore(coreFoundationDir: string, mobileDeviceDir: string): boolean { 61 | return this.$fs.exists(coreFoundationDir) && this.$fs.exists(mobileDeviceDir); 62 | } 63 | } 64 | $injector.register("iTunesValidator", ITunesValidator); 65 | -------------------------------------------------------------------------------- /dispatchers.ts: -------------------------------------------------------------------------------- 1 | import * as queue from "./queue"; 2 | import * as path from "path"; 3 | 4 | export class CommandDispatcher implements ICommandDispatcher { 5 | constructor(private $logger: ILogger, 6 | private $cancellation: ICancellationService, 7 | private $commandsService: ICommandsService, 8 | private $staticConfig: Config.IStaticConfig, 9 | private $sysInfo: ISysInfo, 10 | private $options: ICommonOptions, 11 | private $fs: IFileSystem) { } 12 | 13 | public async dispatchCommand(): Promise { 14 | if (this.$options.version) { 15 | return this.printVersion(); 16 | } 17 | 18 | if (this.$logger.getLevel() === "TRACE") { 19 | // CommandDispatcher is called from external CLI's only, so pass the path to their package.json 20 | const sysInfo = await this.$sysInfo.getSysInfo({pathToNativeScriptCliPackageJson: path.join(__dirname, "..", "..", "package.json")}); 21 | this.$logger.trace("System information:"); 22 | this.$logger.trace(sysInfo); 23 | } 24 | 25 | let commandName = this.getCommandName(); 26 | const commandArguments = this.$options.argv._.slice(1); 27 | const lastArgument: string = _.last(commandArguments); 28 | 29 | if (this.$options.help) { 30 | commandArguments.unshift(commandName); 31 | commandName = "help"; 32 | } else if (lastArgument === "/?" || lastArgument === "?") { 33 | commandArguments.pop(); 34 | commandArguments.unshift(commandName); 35 | commandName = "help"; 36 | } 37 | 38 | await this.$cancellation.begin("cli"); 39 | 40 | await this.$commandsService.tryExecuteCommand(commandName, commandArguments); 41 | } 42 | 43 | public async completeCommand(): Promise { 44 | return this.$commandsService.completeCommand(); 45 | } 46 | 47 | private getCommandName(): string { 48 | const remaining: string[] = this.$options.argv._; 49 | if (remaining.length > 0) { 50 | return remaining[0].toString().toLowerCase(); 51 | } 52 | // if only is specified on console, show console help 53 | this.$options.help = true; 54 | return ""; 55 | } 56 | 57 | private printVersion(): void { 58 | let version = this.$staticConfig.version; 59 | 60 | const json = this.$fs.readJson(this.$staticConfig.pathToPackageJson); 61 | if (json && json.buildVersion) { 62 | version = `${version}-${json.buildVersion}`; 63 | } 64 | this.$logger.out(version); 65 | } 66 | } 67 | $injector.register("commandDispatcher", CommandDispatcher); 68 | 69 | class FutureDispatcher implements IFutureDispatcher { 70 | private actions: IQueue; 71 | 72 | public constructor(private $errors: IErrors) { } 73 | 74 | public async run(): Promise { 75 | if (this.actions) { 76 | this.$errors.fail("You cannot run a running future dispatcher."); 77 | } 78 | this.actions = new queue.Queue(); 79 | 80 | while (true) { 81 | const action = await this.actions.dequeue(); 82 | await action(); 83 | } 84 | } 85 | 86 | public dispatch(action: () => Promise) { 87 | this.actions.enqueue(action); 88 | } 89 | } 90 | $injector.register("dispatcher", FutureDispatcher, false); 91 | -------------------------------------------------------------------------------- /test/unit-tests/mobile/genymotion/virtualbox-service.ts: -------------------------------------------------------------------------------- 1 | import { Yok } from "../../../../yok"; 2 | import { VirtualBoxService } from "../../../../mobile/android/genymotion/virtualbox-service"; 3 | import { assert } from "chai"; 4 | 5 | function createTestInjector() { 6 | const testInjector = new Yok(); 7 | 8 | testInjector.register("childProcess", { 9 | trySpawnFromCloseEvent: () => ({}) 10 | }); 11 | testInjector.register("fs", {}); 12 | testInjector.register("logger", {}); 13 | testInjector.register("hostInfo", {}); 14 | testInjector.register("virtualBoxService", VirtualBoxService); 15 | 16 | return testInjector; 17 | } 18 | 19 | describe("VirtualBoxService", () => { 20 | let injector: IInjector = null; 21 | let virtualBoxService: Mobile.IVirtualBoxService = null; 22 | 23 | beforeEach(() => { 24 | injector = createTestInjector(); 25 | virtualBoxService = injector.resolve("virtualBoxService"); 26 | }); 27 | 28 | function mockData(opts: { isVirtualBoxInstalled: boolean, spawnOutput?: { stdout: string, stderr: string } }) { 29 | const fs = injector.resolve("fs"); 30 | fs.exists = () => opts.isVirtualBoxInstalled; 31 | 32 | const childProcess = injector.resolve("childProcess"); 33 | childProcess.trySpawnFromCloseEvent = () => opts.spawnOutput; 34 | } 35 | 36 | describe("listVms", () => { 37 | it("should return an empty array when virtualbox is not configured", async () => { 38 | mockData({isVirtualBoxInstalled: false}); 39 | const output = await virtualBoxService.listVms(); 40 | assert.deepEqual(output.vms, []); 41 | assert.isNull(output.error); 42 | }); 43 | it("should return correctly virtual machines", async () => { 44 | mockData({isVirtualBoxInstalled: true, spawnOutput: { stdout: '"test" {4a1bf7cd-a7b4-45ef-8cb0-c5a0aafad211}', stderr: null }}); 45 | const output = await virtualBoxService.listVms(); 46 | assert.deepEqual(output.vms, [{id: "4a1bf7cd-a7b4-45ef-8cb0-c5a0aafad211", name: "test"}]); 47 | assert.isNull(output.error); 48 | }); 49 | it("should return the error when some error is thrown", async () => { 50 | const error = "some error is thrown"; 51 | mockData({isVirtualBoxInstalled: true, spawnOutput: { stdout: null, stderr: error}}); 52 | const output = await virtualBoxService.listVms(); 53 | assert.deepEqual(output.vms, []); 54 | assert.deepEqual(output.error, error); 55 | }); 56 | }); 57 | 58 | describe("enumerateGuestProperties", () => { 59 | it("should return null when virtualbox is not configured", async () => { 60 | mockData({isVirtualBoxInstalled: false, spawnOutput: { stdout: "", stderr: null }}); 61 | const output = await virtualBoxService.enumerateGuestProperties("testId"); 62 | assert.isNull(output.properties); 63 | assert.isNull(output.error); 64 | }); 65 | it("should return the error when some error is thrown", async () => { 66 | const error = "some error"; 67 | mockData({isVirtualBoxInstalled: true, spawnOutput: { stdout: null, stderr: error }}); 68 | const output = await virtualBoxService.enumerateGuestProperties("testId"); 69 | assert.isNull(output.properties); 70 | assert.deepEqual(output.error, error); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /decorators.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Caches the result of the first execution of the method and returns it whenever it is called instead of executing it again. 3 | * Works with methods and getters. 4 | * @example 5 | * ``` 6 | * class CacheDecoratorsTest { 7 | * 8 | * @cache() 9 | * public method(num: number): number { 10 | * return num; 11 | * } 12 | * 13 | * @cache() 14 | * public get property(): any { 15 | * // execute some heavy operation. 16 | * return result; 17 | * } 18 | * } 19 | * 20 | * const instance = new CacheDecoratorsTest(); 21 | * const result = instance.method(1); // returns 1; 22 | * 23 | * // all consecutive calls to instance.method will return 1. 24 | * const result2 = instance.method(2); // returns 1; 25 | * ``` 26 | */ 27 | export function cache(): any { 28 | return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 29 | let result: any; 30 | const propName: string = descriptor.value ? "value" : "get"; 31 | 32 | const originalValue = (descriptor)[propName]; 33 | 34 | (descriptor)[propName] = function (...args: any[]) { 35 | const propertyName = `__isCalled_${propertyKey}__`; 36 | if (this && !this[propertyName]) { 37 | this[propertyName] = true; 38 | result = originalValue.apply(this, args); 39 | } 40 | 41 | return result; 42 | }; 43 | 44 | return descriptor; 45 | }; 46 | } 47 | 48 | /** 49 | * Calls specific method of the instance before executing the decorated method. 50 | * This is usable when some of your methods depend on initialize async method, that cannot be invoked in constructor of the class. 51 | * IMPORTANT: The decorated method must be async. 52 | * @param {string} methodName The name of the method that will be invoked before calling the decorated method. 53 | * @param {any[]} methodArgs Args that will be passed to the method that will be invoked before calling the decorated one. 54 | * @return {any} Result of the decorated method. 55 | */ 56 | export function invokeBefore(methodName: string, methodArgs?: any[]): any { 57 | return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 58 | const originalValue = descriptor.value; 59 | descriptor.value = async function (...args: any[]) { 60 | await target[methodName].apply(this, methodArgs); 61 | return originalValue.apply(this, args); 62 | }; 63 | 64 | return descriptor; 65 | }; 66 | } 67 | 68 | export function invokeInit(): any { 69 | return invokeBefore("init"); 70 | } 71 | 72 | export function exported(moduleName: string): any { 73 | return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 74 | $injector.publicApi.__modules__[moduleName] = $injector.publicApi.__modules__[moduleName] || {}; 75 | $injector.publicApi.__modules__[moduleName][propertyKey] = (...args: any[]): any => { 76 | const originalModule = $injector.resolve(moduleName), 77 | originalMethod: any = originalModule[propertyKey], 78 | result = originalMethod.apply(originalModule, args); 79 | 80 | return result; 81 | }; 82 | 83 | return descriptor; 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /mobile/ios/simulator/ios-simulator-device.ts: -------------------------------------------------------------------------------- 1 | import * as applicationManagerPath from "./ios-simulator-application-manager"; 2 | import * as fileSystemPath from "./ios-simulator-file-system"; 3 | import * as constants from "../../../constants"; 4 | import { cache } from "../../../decorators"; 5 | 6 | export class IOSSimulator implements Mobile.IiOSSimulator { 7 | private _applicationManager: Mobile.IDeviceApplicationManager; 8 | private _fileSystem: Mobile.IDeviceFileSystem; 9 | private _deviceLogHandler: Function; 10 | 11 | constructor(private simulator: Mobile.IiSimDevice, 12 | private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, 13 | private $deviceLogProvider: Mobile.IDeviceLogProvider, 14 | private $injector: IInjector, 15 | private $iOSSimResolver: Mobile.IiOSSimResolver, 16 | private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider) { } 17 | 18 | public get deviceInfo(): Mobile.IDeviceInfo { 19 | return { 20 | imageIdentifier: this.simulator.id, 21 | identifier: this.simulator.id, 22 | displayName: this.simulator.name, 23 | model: _.last(this.simulator.fullId.split(".")), 24 | version: this.simulator.runtimeVersion, 25 | vendor: "Apple", 26 | platform: this.$devicePlatformsConstants.iOS, 27 | status: constants.CONNECTED_STATUS, 28 | errorHelp: null, 29 | isTablet: this.simulator.fullId.toLowerCase().indexOf("ipad") !== -1, 30 | type: constants.DeviceTypes.Emulator 31 | }; 32 | } 33 | 34 | public get isEmulator(): boolean { 35 | return true; 36 | } 37 | 38 | public async getApplicationInfo(applicationIdentifier: string): Promise { 39 | return this.applicationManager.getApplicationInfo(applicationIdentifier); 40 | } 41 | 42 | public get applicationManager(): Mobile.IDeviceApplicationManager { 43 | if (!this._applicationManager) { 44 | this._applicationManager = this.$injector.resolve(applicationManagerPath.IOSSimulatorApplicationManager, { iosSim: this.$iOSSimResolver.iOSSim, device: this }); 45 | } 46 | 47 | return this._applicationManager; 48 | } 49 | 50 | public get fileSystem(): Mobile.IDeviceFileSystem { 51 | if (!this._fileSystem) { 52 | this._fileSystem = this.$injector.resolve(fileSystemPath.IOSSimulatorFileSystem, { iosSim: this.$iOSSimResolver.iOSSim }); 53 | } 54 | 55 | return this._fileSystem; 56 | } 57 | 58 | @cache() 59 | public async openDeviceLogStream(options?: Mobile.IiOSLogStreamOptions): Promise { 60 | this._deviceLogHandler = this.onDeviceLog.bind(this, options); 61 | this.$iOSSimulatorLogProvider.on(constants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler); 62 | return this.$iOSSimulatorLogProvider.startLogProcess(this.simulator.id, options); 63 | } 64 | 65 | public detach(): void { 66 | if (this._deviceLogHandler) { 67 | this.$iOSSimulatorLogProvider.removeListener(constants.DEVICE_LOG_EVENT_NAME, this._deviceLogHandler); 68 | } 69 | } 70 | 71 | private onDeviceLog(options: Mobile.IiOSLogStreamOptions, response: IOSDeviceLib.IDeviceLogData): void { 72 | if (response.deviceId === this.deviceInfo.identifier && !(response).muted) { 73 | this.$deviceLogProvider.logData(response.message, this.$devicePlatformsConstants.iOS, this.deviceInfo.identifier); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /mobile/emulator-helper.ts: -------------------------------------------------------------------------------- 1 | import { RUNNING_EMULATOR_STATUS, DeviceTypes } from "../constants"; 2 | 3 | export class EmulatorHelper implements Mobile.IEmulatorHelper { 4 | // https://developer.android.com/guide/topics/manifest/uses-sdk-element 5 | public mapAndroidApiLevelToVersion = { 6 | "android-28": "9.0.0", 7 | "android-27": "8.1.0", 8 | "android-26": "8.0.0", 9 | "android-25": "7.1.1", 10 | "android-24": "7.0.0", 11 | "android-23": "6.0.0", 12 | "android-22": "5.1.0", 13 | "android-21": "5.0.0", 14 | "android-20": "4.4.0", 15 | "android-19": "4.4.0", 16 | "android-18": "4.3.0", 17 | "android-17": "4.2.2" 18 | }; 19 | 20 | public getEmulatorsFromAvailableEmulatorsOutput(availableEmulatorsOutput: Mobile.IListEmulatorsOutput): Mobile.IDeviceInfo[] { 21 | return (_(availableEmulatorsOutput) 22 | .valuesIn() 23 | .map((value: Mobile.IEmulatorImagesOutput) => value.devices) 24 | .concat() 25 | .flatten() 26 | .value()); 27 | } 28 | 29 | public getErrorsFromAvailableEmulatorsOutput(availableEmulatorsOutput: Mobile.IListEmulatorsOutput): string[] { 30 | return (_(availableEmulatorsOutput) 31 | .valuesIn() 32 | .map((value: Mobile.IEmulatorImagesOutput) => value.errors) 33 | .concat() 34 | .flatten() 35 | .value()); 36 | } 37 | 38 | public getEmulatorByImageIdentifier(imageIdentifier: string, emulators: Mobile.IDeviceInfo[]): Mobile.IDeviceInfo { 39 | const imagerIdentifierLowerCase = imageIdentifier && imageIdentifier.toLowerCase(); 40 | return _.find(emulators, emulator => emulator && emulator.imageIdentifier && imageIdentifier && emulator.imageIdentifier.toLowerCase() === imagerIdentifierLowerCase); 41 | } 42 | 43 | public getEmulatorByIdOrName(emulatorIdOrName: string, emulators: Mobile.IDeviceInfo[]): Mobile.IDeviceInfo { 44 | const emulatorIdOrNameLowerCase = emulatorIdOrName && emulatorIdOrName.toLowerCase(); 45 | return _.find(emulators, emulator => emulator && emulatorIdOrNameLowerCase && ((emulator.identifier && emulator.identifier.toLowerCase() === emulatorIdOrNameLowerCase) || emulator.displayName.toLowerCase() === emulatorIdOrNameLowerCase)); 46 | } 47 | 48 | public isEmulatorRunning(emulator: Mobile.IDeviceInfo): boolean { 49 | return emulator && emulator.status === RUNNING_EMULATOR_STATUS; 50 | } 51 | 52 | public getEmulatorByStartEmulatorOptions(options: Mobile.IStartEmulatorOptions, emulators: Mobile.IDeviceInfo[]): Mobile.IDeviceInfo { 53 | let result: Mobile.IDeviceInfo = null; 54 | 55 | if (options.emulator) { 56 | result = options.emulator; 57 | } 58 | 59 | if (!result && options.imageIdentifier) { 60 | result = this.getEmulatorByImageIdentifier(options.imageIdentifier, emulators); 61 | } 62 | 63 | if (!result && options.emulatorIdOrName) { 64 | result = this.getEmulatorByIdOrName(options.emulatorIdOrName, emulators); 65 | } 66 | 67 | return result; 68 | } 69 | 70 | public setRunningAndroidEmulatorProperties(emulatorId: string, emulator: Mobile.IDeviceInfo): void { 71 | emulator.identifier = emulatorId; 72 | emulator.status = RUNNING_EMULATOR_STATUS; 73 | emulator.type = DeviceTypes.Device; 74 | //emulator.isTablet; // TODO: consider to do this here!!! 75 | } 76 | } 77 | $injector.register("emulatorHelper", EmulatorHelper); 78 | -------------------------------------------------------------------------------- /mobile/mobile-helper.ts: -------------------------------------------------------------------------------- 1 | import * as helpers from "../helpers"; 2 | 3 | export class MobileHelper implements Mobile.IMobileHelper { 4 | private static DEVICE_PATH_SEPARATOR = "/"; 5 | private platformNamesCache: string[]; 6 | 7 | constructor(private $mobilePlatformsCapabilities: Mobile.IPlatformsCapabilities, 8 | private $errors: IErrors, 9 | private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { } 10 | 11 | public get platformNames(): string[] { 12 | this.platformNamesCache = this.platformNamesCache || 13 | _.map(this.$mobilePlatformsCapabilities.getPlatformNames(), platform => this.normalizePlatformName(platform)); 14 | 15 | return this.platformNamesCache; 16 | } 17 | 18 | public getPlatformCapabilities(platform: string): Mobile.IPlatformCapabilities { 19 | const platformNames = this.$mobilePlatformsCapabilities.getPlatformNames(); 20 | const validPlatformName = this.validatePlatformName(platform); 21 | if (!_.some(platformNames, platformName => platformName === validPlatformName)) { 22 | this.$errors.failWithoutHelp("'%s' is not a valid device platform. Valid platforms are %s.", platform, platformNames); 23 | } 24 | 25 | return this.$mobilePlatformsCapabilities.getAllCapabilities()[validPlatformName]; 26 | } 27 | 28 | public isAndroidPlatform(platform: string): boolean { 29 | return !!(platform && (this.$devicePlatformsConstants.Android.toLowerCase() === platform.toLowerCase())); 30 | } 31 | 32 | public isiOSPlatform(platform: string): boolean { 33 | return !!(platform && (this.$devicePlatformsConstants.iOS.toLowerCase() === platform.toLowerCase())); 34 | } 35 | 36 | public isWP8Platform(platform: string): boolean { 37 | return !!(platform && (this.$devicePlatformsConstants.WP8.toLowerCase() === platform.toLowerCase())); 38 | } 39 | 40 | public normalizePlatformName(platform: string): string { 41 | if (this.isAndroidPlatform(platform)) { 42 | return "Android"; 43 | } else if (this.isiOSPlatform(platform)) { 44 | return "iOS"; 45 | } else if (this.isWP8Platform(platform)) { 46 | return "WP8"; 47 | } 48 | 49 | return undefined; 50 | } 51 | 52 | public isPlatformSupported(platform: string): boolean { 53 | return _.includes(this.getPlatformCapabilities(platform).hostPlatformsForDeploy, process.platform); 54 | } 55 | 56 | public validatePlatformName(platform: string): string { 57 | if (!platform) { 58 | this.$errors.fail("No device platform specified."); 59 | } 60 | 61 | const normalizedPlatform = this.normalizePlatformName(platform); 62 | if (!normalizedPlatform || !_.includes(this.platformNames, normalizedPlatform)) { 63 | this.$errors.fail("'%s' is not a valid device platform. Valid platforms are %s.", 64 | platform, helpers.formatListOfNames(this.platformNames)); 65 | } 66 | return normalizedPlatform; 67 | } 68 | 69 | public buildDevicePath(...args: string[]): string { 70 | return this.correctDevicePath(args.join(MobileHelper.DEVICE_PATH_SEPARATOR)); 71 | } 72 | 73 | public correctDevicePath(filePath: string): string { 74 | return helpers.stringReplaceAll(filePath, '\\', '/'); 75 | } 76 | 77 | public isiOSTablet(deviceName: string): boolean { 78 | return deviceName && deviceName.toLowerCase().indexOf("ipad") !== -1; 79 | } 80 | } 81 | $injector.register("mobileHelper", MobileHelper); 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobile-cli-lib", 3 | "preferGlobal": false, 4 | "version": "0.22.0", 5 | "author": "Telerik ", 6 | "description": "common lib used by different CLI", 7 | "bin": { 8 | "mobile-cli-lib": "./bin/common-lib.js" 9 | }, 10 | "scripts": { 11 | "mocha": "node test-scripts/mocha.js", 12 | "test": "node test-scripts/istanbul.js", 13 | "tslint": "tslint -p tsconfig.json --type-check" 14 | }, 15 | "main": "./common-lib.js", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/telerik/mobile-cli-lib.git" 19 | }, 20 | "keywords": [ 21 | "cordova", 22 | "appbuilder", 23 | "telerik", 24 | "mobile", 25 | "nativescript", 26 | "Proton" 27 | ], 28 | "dependencies": { 29 | "byline": "4.2.1", 30 | "chalk": "1.0.0", 31 | "cli-table": "https://github.com/telerik/cli-table/tarball/v0.3.1.1", 32 | "colors": "0.6.2", 33 | "cookie": "0.1.0", 34 | "esprima": "2.7.0", 35 | "gaze": "1.0.0", 36 | "iconv-lite": "0.4.3", 37 | "inquirer": "0.8.2", 38 | "ios-device-lib": "~0.3.0", 39 | "ios-sim-portable": "3.2.0", 40 | "lockfile": "1.0.3", 41 | "lodash": "4.17.10", 42 | "log4js": "0.6.9", 43 | "marked": "0.3.12", 44 | "marked-terminal": "2.0.0", 45 | "minimatch": "2.0.4", 46 | "mkdirp": "0.3.5", 47 | "mute-stream": "0.0.4", 48 | "nativescript-doctor": "1.2.0", 49 | "open": "0.0.4", 50 | "osenv": "0.1.0", 51 | "parse5": "2.2.0", 52 | "plist": "1.1.0", 53 | "proxy-lib": "0.2.0", 54 | "pullstream": "https://github.com/icenium/node-pullstream/tarball/master", 55 | "qr-image": "3.2.0", 56 | "qrcode-generator": "1.0.0", 57 | "request": "2.81.0", 58 | "rimraf": "2.2.6", 59 | "semver": "5.5.0", 60 | "shelljs": "0.7.5", 61 | "simple-plist": "0.2.1", 62 | "source-map": "0.5.6", 63 | "tabtab": "https://github.com/Icenium/node-tabtab/tarball/master", 64 | "temp": "0.8.1", 65 | "uuid": "^3.0.0", 66 | "validator": "3.2.1", 67 | "winreg": "0.0.12", 68 | "xmlhttprequest": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", 69 | "yargs": "6.0.0", 70 | "zipstream": "https://github.com/Icenium/node-zipstream/tarball/master" 71 | }, 72 | "analyze": true, 73 | "devDependencies": { 74 | "@types/chai": "4.0.1", 75 | "@types/chai-as-promised": "0.0.31", 76 | "@types/lockfile": "1.0.0", 77 | "@types/lodash": "4.14.116", 78 | "@types/node": "6.0.61", 79 | "@types/qr-image": "3.2.0", 80 | "@types/request": "0.0.45", 81 | "@types/semver": "5.5.0", 82 | "@types/source-map": "0.5.0", 83 | "chai": "4.0.2", 84 | "chai-as-promised": "7.0.0", 85 | "file": "0.2.2", 86 | "grunt": "1.0.1", 87 | "grunt-contrib-clean": "1.0.0", 88 | "grunt-contrib-watch": "1.0.0", 89 | "grunt-shell": "2.1.0", 90 | "grunt-ts": "6.0.0-beta.16", 91 | "istanbul": "0.4.5", 92 | "mocha": "3.2.0", 93 | "spec-xunit-file": "0.0.1-3", 94 | "tslint": "5.4.3", 95 | "typescript": "2.4.1" 96 | }, 97 | "bundledDependencies": [], 98 | "license": "Apache-2.0", 99 | "engines": { 100 | "node": ">=4.2.1 <5.0.0 || >=5.1.0 <8.0.0" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /validators/project-name-validator.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as helpers from "../helpers"; 3 | import * as ValidationResult from "./validation-result"; 4 | 5 | export class ProjectNameValidator implements IProjectNameValidator { 6 | private static MAX_FILENAME_LENGTH = 30; 7 | private static EMPTY_FILENAME_ERROR_MESSAGE = "Name cannot be empty."; 8 | private static NOT_VALID_NAME_ERROR_MESSAGE = "Name should contain only the following symbols: A-Z, a-z, 0-9, _, ., - and space"; 9 | private static RESERVED_NAME_ERROR_MESSAGE = "Name is among the reserved names: ~, CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9."; 10 | private static INVALID_EXTENSION_ERROR_MESSAGE = "Unsupported file type."; 11 | private static TOO_LONG_NAME_ERROR_MESSAGE = "Name is too long."; 12 | private static TRAILING_DOTS_ERROR_MESSAGE = "Name cannot contain trailing dots."; 13 | private static LEADING_SPACES_ERROR_MESSAGE = "Name cannot contain leading spaces."; 14 | private static TRAILING_SPACES_ERROR_MESSAGE = "Name cannot contain trailing spaces."; 15 | private static INVALID_FILENAMES = ["~", "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"]; 16 | private static INVALID_EXTENSIONS: string[] = []; 17 | 18 | constructor(private $errors: IErrors) { } 19 | 20 | private validateName(name: string): ValidationResult.ValidationResult { 21 | const validNameRegex = /^[a-zA-Z0-9_.\- ]*$/g; 22 | const ext = path.extname(name); 23 | 24 | if (helpers.isNullOrWhitespace(name)) { 25 | return new ValidationResult.ValidationResult(ProjectNameValidator.EMPTY_FILENAME_ERROR_MESSAGE); 26 | } 27 | if (!validNameRegex.test(name)) { 28 | return new ValidationResult.ValidationResult(ProjectNameValidator.NOT_VALID_NAME_ERROR_MESSAGE); 29 | } 30 | if (_.includes(ProjectNameValidator.INVALID_FILENAMES, name.split(".")[0])) { 31 | return new ValidationResult.ValidationResult(ProjectNameValidator.RESERVED_NAME_ERROR_MESSAGE); 32 | } 33 | if (ext !== "" && _.includes(ProjectNameValidator.INVALID_EXTENSIONS, ext)) { 34 | return new ValidationResult.ValidationResult(ProjectNameValidator.INVALID_EXTENSION_ERROR_MESSAGE); 35 | } 36 | if (name.length > ProjectNameValidator.MAX_FILENAME_LENGTH) { 37 | return new ValidationResult.ValidationResult(ProjectNameValidator.TOO_LONG_NAME_ERROR_MESSAGE); 38 | } 39 | if (_.startsWith(name, " ")) { 40 | return new ValidationResult.ValidationResult(ProjectNameValidator.LEADING_SPACES_ERROR_MESSAGE); 41 | } 42 | if (_.endsWith(name, ".")) { 43 | return new ValidationResult.ValidationResult(ProjectNameValidator.TRAILING_DOTS_ERROR_MESSAGE); 44 | } 45 | if (_.endsWith(name, " ")) { 46 | return new ValidationResult.ValidationResult(ProjectNameValidator.TRAILING_SPACES_ERROR_MESSAGE); 47 | } 48 | 49 | return ValidationResult.ValidationResult.Successful; 50 | } 51 | 52 | public validate(name: string): boolean { 53 | const validationResult: ValidationResult.ValidationResult = this.validateName(name); 54 | const isSuccessful = validationResult.isSuccessful; 55 | 56 | if (!isSuccessful) { 57 | this.$errors.fail(validationResult.error); 58 | } 59 | 60 | return isSuccessful; 61 | } 62 | } 63 | $injector.register("projectNameValidator", ProjectNameValidator); 64 | -------------------------------------------------------------------------------- /verify-node-version.ts: -------------------------------------------------------------------------------- 1 | 2 | // This function must be separate to avoid dependencies on C++ modules - it must execute precisely when other functions cannot 3 | 4 | // Use only ES5 code here - pure JavaScript can be executed with any Node.js version (even 0.10, 0.12). 5 | /* tslint:disable:no-var-keyword no-var-requires prefer-const*/ 6 | var os = require("os"); 7 | var semver = require("semver"); 8 | var util = require("util"); 9 | 10 | // These versions cannot be used with CLI due to bugs in the node itself. 11 | // We are absolutely sure we cannot work with them, so inform the user if he is trying to use any of them and exit the process. 12 | var versionsCausingFailure = ["0.10.34", "4.0.0", "4.2.0", "5.0.0"]; 13 | var minimumRequiredVersion = "6.0.0"; 14 | 15 | interface INodeVersionOpts { 16 | supportedVersionsRange: string; 17 | cliName: string; 18 | deprecatedVersions?: string[]; 19 | nodeVer: string; 20 | } 21 | 22 | function getNodeVersionOpts(): INodeVersionOpts { 23 | var supportedVersionsRange = require("../../package.json").engines.node; 24 | var cliName = "NativeScript"; 25 | var deprecatedVersions = ["^6.0.0", "^7.0.0"]; 26 | var nodeVer = process.version.substr(1); 27 | return { 28 | supportedVersionsRange: supportedVersionsRange, 29 | cliName: cliName, 30 | nodeVer: nodeVer, 31 | deprecatedVersions: deprecatedVersions 32 | }; 33 | } 34 | 35 | export function verifyNodeVersion(): void { 36 | var verificationOpts = getNodeVersionOpts(); 37 | var cliName = verificationOpts.cliName; 38 | var supportedVersionsRange = verificationOpts.supportedVersionsRange; 39 | var nodeVer = verificationOpts.nodeVer; 40 | 41 | // The colors module should not be assigned to variable because the lint task will fail for not used variable. 42 | require("colors"); 43 | 44 | if (versionsCausingFailure.indexOf(nodeVer) !== -1 || !semver.valid(nodeVer) || semver.lt(nodeVer, minimumRequiredVersion)) { 45 | console.error(util.format("%sNode.js '%s' is not supported. To be able to work with %s CLI, install any Node.js version in the following range: %s.%s", 46 | os.EOL, nodeVer, cliName, supportedVersionsRange, os.EOL).red.bold); 47 | process.exit(1); 48 | } 49 | 50 | var nodeWarning = getNodeWarning(); 51 | if (nodeWarning) { 52 | console.warn((os.EOL + nodeWarning + os.EOL).yellow.bold); 53 | } 54 | } 55 | 56 | export function getNodeWarning(): string { 57 | var verificationOpts = getNodeVersionOpts(); 58 | var cliName = verificationOpts.cliName; 59 | var supportedVersionsRange = verificationOpts.supportedVersionsRange; 60 | var deprecatedVersions = verificationOpts.deprecatedVersions; 61 | var nodeVer = verificationOpts.nodeVer; 62 | 63 | var warningMessage = ""; 64 | if (deprecatedVersions) { 65 | deprecatedVersions.forEach(function (version) { 66 | if (semver.satisfies(nodeVer, version)) { 67 | warningMessage = "Support for Node.js " + version + " is deprecated and will be removed in one of the next releases of " + cliName + 68 | ". Please, upgrade to the latest Node.js LTS version. "; 69 | return warningMessage; 70 | } 71 | }); 72 | } 73 | 74 | if (!warningMessage) { 75 | var checkSatisfied = semver.satisfies(nodeVer, supportedVersionsRange); 76 | if (!checkSatisfied) { 77 | warningMessage = "Support for Node.js " + nodeVer + " is not verified. " + cliName + " CLI might not install or run properly."; 78 | } 79 | } 80 | 81 | return warningMessage; 82 | } 83 | /* tslint:enable */ 84 | -------------------------------------------------------------------------------- /commands/autocompletion.ts: -------------------------------------------------------------------------------- 1 | import * as helpers from "../helpers"; 2 | 3 | export class AutoCompleteCommand implements ICommand { 4 | constructor(private $autoCompletionService: IAutoCompletionService, 5 | private $logger: ILogger, 6 | private $prompter: IPrompter) { 7 | } 8 | 9 | public disableAnalytics = true; 10 | public allowedParameters: ICommandParameter[] = []; 11 | 12 | public async execute(args: string[]): Promise { 13 | if (helpers.isInteractive()) { 14 | if (this.$autoCompletionService.isAutoCompletionEnabled()) { 15 | if (this.$autoCompletionService.isObsoleteAutoCompletionEnabled()) { 16 | // obsolete autocompletion is enabled, update it to the new one: 17 | await this.$autoCompletionService.enableAutoCompletion(); 18 | } else { 19 | this.$logger.info("Autocompletion is already enabled"); 20 | } 21 | } else { 22 | this.$logger.out("If you are using bash or zsh, you can enable command-line completion."); 23 | const message = "Do you want to enable it now?"; 24 | 25 | const autoCompetionStatus = await this.$prompter.confirm(message, () => true); 26 | if (autoCompetionStatus) { 27 | await this.$autoCompletionService.enableAutoCompletion(); 28 | } else { 29 | // make sure we've removed all autocompletion code from all shell profiles 30 | this.$autoCompletionService.disableAutoCompletion(); 31 | } 32 | } 33 | } 34 | } 35 | } 36 | $injector.registerCommand("autocomplete|*default", AutoCompleteCommand); 37 | 38 | export class DisableAutoCompleteCommand implements ICommand { 39 | constructor(private $autoCompletionService: IAutoCompletionService, 40 | private $logger: ILogger) { 41 | } 42 | 43 | public disableAnalytics = true; 44 | public allowedParameters: ICommandParameter[] = []; 45 | 46 | public async execute(args: string[]): Promise { 47 | if (this.$autoCompletionService.isAutoCompletionEnabled()) { 48 | this.$autoCompletionService.disableAutoCompletion(); 49 | } else { 50 | this.$logger.info("Autocompletion is already disabled."); 51 | } 52 | } 53 | } 54 | $injector.registerCommand("autocomplete|disable", DisableAutoCompleteCommand); 55 | 56 | export class EnableAutoCompleteCommand implements ICommand { 57 | constructor(private $autoCompletionService: IAutoCompletionService, 58 | private $logger: ILogger) { } 59 | 60 | public disableAnalytics = true; 61 | public allowedParameters: ICommandParameter[] = []; 62 | 63 | public async execute(args: string[]): Promise { 64 | if (this.$autoCompletionService.isAutoCompletionEnabled()) { 65 | this.$logger.info("Autocompletion is already enabled."); 66 | } else { 67 | await this.$autoCompletionService.enableAutoCompletion(); 68 | } 69 | } 70 | } 71 | $injector.registerCommand("autocomplete|enable", EnableAutoCompleteCommand); 72 | 73 | export class AutoCompleteStatusCommand implements ICommand { 74 | constructor(private $autoCompletionService: IAutoCompletionService, 75 | private $logger: ILogger) { } 76 | 77 | public disableAnalytics = true; 78 | public allowedParameters: ICommandParameter[] = []; 79 | 80 | public async execute(args: string[]): Promise { 81 | if (this.$autoCompletionService.isAutoCompletionEnabled()) { 82 | this.$logger.info("Autocompletion is enabled."); 83 | } else { 84 | this.$logger.info("Autocompletion is disabled."); 85 | } 86 | } 87 | } 88 | $injector.registerCommand("autocomplete|status", AutoCompleteStatusCommand); 89 | -------------------------------------------------------------------------------- /test/unit-tests/process-service.ts: -------------------------------------------------------------------------------- 1 | import { Yok } from "../../yok"; 2 | import { ProcessService } from "../../services/process-service"; 3 | import { assert } from "chai"; 4 | 5 | const processExitSignals = ["exit", "SIGINT", "SIGTERM"]; 6 | const emptyFunction = () => { /* no implementation required */ }; 7 | function createTestInjector(): IInjector { 8 | const testInjector = new Yok(); 9 | 10 | testInjector.register("processService", ProcessService); 11 | 12 | return testInjector; 13 | } 14 | 15 | describe("Process service", () => { 16 | let testInjector: IInjector; 17 | let $processService: IProcessService; 18 | 19 | beforeEach(() => { 20 | testInjector = createTestInjector(); 21 | $processService = testInjector.resolve("processService"); 22 | }); 23 | 24 | it("should not add only one listener for the exit, SIGIN and SIGTERM events.", () => { 25 | $processService.attachToProcessExitSignals({}, emptyFunction); 26 | $processService.attachToProcessExitSignals({}, emptyFunction); 27 | 28 | _.each(processExitSignals, (signal: string) => { 29 | // We need to search only for our listener because each exit signal have different listeners added to it. 30 | const actualListeners = _.filter(process.listeners(signal), (listener: Function) => listener.toString().indexOf("executeAllCallbacks") >= 0); 31 | assert.deepEqual(actualListeners.length, 1); 32 | }); 33 | }); 34 | 35 | it("should add listener with context only once if there already is callback with the same context.", () => { 36 | const context = { test: "test" }; 37 | const listener = () => 42; 38 | 39 | $processService.attachToProcessExitSignals(context, listener); 40 | $processService.attachToProcessExitSignals(context, listener); 41 | 42 | assert.deepEqual($processService.listenersCount, 1); 43 | }); 44 | 45 | it("should add two different listeners for one context.", () => { 46 | const context = { test: "test" }; 47 | const numberListener = () => 42; 48 | const booleanListener = () => true; 49 | 50 | $processService.attachToProcessExitSignals(context, numberListener); 51 | $processService.attachToProcessExitSignals(context, booleanListener); 52 | 53 | assert.deepEqual($processService.listenersCount, 2); 54 | }); 55 | 56 | it("should add one listener with different context twice.", () => { 57 | const listener = () => 42; 58 | 59 | $processService.attachToProcessExitSignals({}, listener); 60 | $processService.attachToProcessExitSignals({}, listener); 61 | 62 | assert.deepEqual($processService.listenersCount, 2); 63 | }); 64 | 65 | it("should execute all attached listeners.", () => { 66 | let hasCalledFirstListener = false; 67 | let hasCalledSecondListener = false; 68 | let hasCalledThirdListener = false; 69 | 70 | const firstListener = () => { 71 | hasCalledFirstListener = true; 72 | }; 73 | 74 | const secondListener = () => { 75 | hasCalledSecondListener = true; 76 | }; 77 | 78 | const thirdListener = () => { 79 | hasCalledThirdListener = true; 80 | }; 81 | 82 | $processService.attachToProcessExitSignals({}, firstListener); 83 | $processService.attachToProcessExitSignals({}, secondListener); 84 | $processService.attachToProcessExitSignals({}, thirdListener); 85 | 86 | // Do not use exit or SIGINT because the tests after this one will not be executed. 87 | global.process.emit("SIGTERM"); 88 | 89 | assert.isTrue(hasCalledFirstListener); 90 | assert.isTrue(hasCalledSecondListener); 91 | assert.isTrue(hasCalledThirdListener); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /services/message-contract-generator.ts: -------------------------------------------------------------------------------- 1 | import { Block } from "../codeGeneration/code-entity"; 2 | import { CodePrinter } from "../codeGeneration/code-printer"; 3 | 4 | export class MessageContractGenerator implements IServiceContractGenerator { 5 | private pendingModels: any; 6 | 7 | constructor(private $fs: IFileSystem, 8 | private $messagesService: IMessagesService) { 9 | this.pendingModels = {}; 10 | } 11 | 12 | public async generate(): Promise { 13 | const interfacesFile = new Block(); 14 | const implementationsFile = new Block(); 15 | 16 | implementationsFile.writeLine("//"); 17 | implementationsFile.writeLine("// automatically generated code; do not edit manually!"); 18 | implementationsFile.writeLine("//"); 19 | implementationsFile.writeLine("/* tslint:disable:all */"); 20 | implementationsFile.writeLine(""); 21 | 22 | interfacesFile.writeLine("//"); 23 | interfacesFile.writeLine("// automatically generated code; do not edit manually!"); 24 | interfacesFile.writeLine("//"); 25 | interfacesFile.writeLine("/* tslint:disable:all */"); 26 | 27 | const messagesClass = new Block("export class Messages implements IMessages"); 28 | const messagesInterface = new Block("interface IMessages"); 29 | 30 | _.each(this.$messagesService.pathsToMessageJsonFiles, jsonFilePath => { 31 | const jsonContents = this.$fs.readJson(jsonFilePath), 32 | implementationBlock: CodeGeneration.IBlock = new Block(), 33 | interfaceBlock: CodeGeneration.IBlock = new Block(); 34 | 35 | this.generateFileRecursive(jsonContents, "", implementationBlock, 0, { shouldGenerateInterface: false }); 36 | this.generateFileRecursive(jsonContents, "", interfaceBlock, 0, { shouldGenerateInterface: true }); 37 | messagesClass.addBlock(implementationBlock); 38 | messagesInterface.addBlock(interfaceBlock); 39 | }); 40 | 41 | interfacesFile.addBlock(messagesInterface); 42 | interfacesFile.writeLine("/* tslint:enable */"); 43 | interfacesFile.writeLine(""); 44 | 45 | implementationsFile.addBlock(messagesClass); 46 | implementationsFile.writeLine("$injector.register('messages', Messages);"); 47 | implementationsFile.writeLine("/* tslint:enable */"); 48 | implementationsFile.writeLine(""); 49 | 50 | const codePrinter = new CodePrinter(); 51 | return { 52 | interfaceFile: codePrinter.composeBlock(interfacesFile), 53 | implementationFile: codePrinter.composeBlock(implementationsFile) 54 | }; 55 | } 56 | 57 | private generateFileRecursive(jsonContents: any, propertyValue: string, block: CodeGeneration.IBlock, depth: number, options: { shouldGenerateInterface: boolean }): void { 58 | _.each(jsonContents, (val: any, key: string) => { 59 | let newPropertyValue = propertyValue + key; 60 | const separator = options.shouldGenerateInterface || depth ? ":" : "="; 61 | const endingSymbol = options.shouldGenerateInterface || !depth ? ";" : ","; 62 | 63 | if (typeof val === "string") { 64 | const actualValue = options.shouldGenerateInterface ? "string" : `"${newPropertyValue}"`; 65 | 66 | block.writeLine(`${key}${separator} ${actualValue}${endingSymbol}`); 67 | newPropertyValue = propertyValue; 68 | return; 69 | } 70 | 71 | const newBlock = new Block(`${key} ${separator} `); 72 | newBlock.endingCharacter = endingSymbol; 73 | this.generateFileRecursive(val, newPropertyValue + ".", newBlock, depth + 1, options); 74 | block.addBlock(newBlock); 75 | }); 76 | } 77 | } 78 | $injector.register("messageContractGenerator", MessageContractGenerator); 79 | --------------------------------------------------------------------------------