diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index a7f613b1d5..1dddd24a3c 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -29,7 +29,11 @@ export class PreviewCommand implements ICommand { env: this.$options.env }); - await this.$previewQrCodeService.printLiveSyncQrCode({ useHotModuleReload: this.$options.hmr, link: this.$options.link }); + await this.$previewQrCodeService.printLiveSyncQrCode({ + projectDir: this.$projectData.projectDir, + useHotModuleReload: this.$options.hmr, + link: this.$options.link + }); } public async canExecute(args: string[]): Promise { diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index aa1369441a..5e096b0368 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -596,7 +596,8 @@ declare const enum ErrorCodes { KARMA_FAIL = 130, UNHANDLED_REJECTION_FAILURE = 131, DELETED_KILL_FILE = 132, - TESTS_INIT_REQUIRED = 133 + TESTS_INIT_REQUIRED = 133, + ALL_DEVICES_DISCONNECTED = 134 } interface IFutureDispatcher { diff --git a/lib/common/mobile/android/android-device.ts b/lib/common/mobile/android/android-device.ts index 81a2623cbb..a98c2b0528 100644 --- a/lib/common/mobile/android/android-device.ts +++ b/lib/common/mobile/android/android-device.ts @@ -71,11 +71,16 @@ export class AndroidDevice implements Mobile.IAndroidDevice { const adbStatusInfo = AndroidDevice.ADB_DEVICE_STATUS_INFO[this.status]; const type = await this.getType(); + let version = details.release; + if (version && version.toLowerCase() === 'q') { + version = '10.0.0'; + } + this.deviceInfo = { identifier: this.identifier, displayName: details.name, model: details.model, - version: details.release, + version, vendor: details.brand, platform: this.$devicePlatformsConstants.Android, status: adbStatusInfo ? adbStatusInfo.deviceStatus : this.status, diff --git a/lib/constants.ts b/lib/constants.ts index ed6b281de0..a1a8961018 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,4 +1,5 @@ require("colors"); +import { PreviewAppLiveSyncEvents } from "./services/livesync/playground/preview-app-constants"; export const APP_FOLDER_NAME = "app"; export const APP_RESOURCES_FOLDER_NAME = "App_Resources"; @@ -269,3 +270,13 @@ export class AndroidAppBundleMessages { public static ANDROID_APP_BUNDLE_DOCS_MESSAGE = "What is Android App Bundle: https://docs.nativescript.org/tooling/publishing/android-app-bundle"; public static ANDROID_APP_BUNDLE_PUBLISH_DOCS_MESSAGE = "How to use Android App Bundle for publishing: https://docs.nativescript.org/tooling/publishing/publishing-android-apps#android-app-bundle"; } + +export const LiveSyncEvents = { + liveSyncStopped: "liveSyncStopped", + // In case we name it error, EventEmitter expects instance of Error to be raised and will also raise uncaught exception in case there's no handler + liveSyncError: "liveSyncError", + previewAppLiveSyncError: PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, + liveSyncExecuted: "liveSyncExecuted", + liveSyncStarted: "liveSyncStarted", + liveSyncNotification: "notify" +}; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 717f41693f..b71cb07b93 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -1,629 +1,634 @@ -// This interface is a mashup of NodeJS' along with Chokidar's event watchers -interface IFSWatcher extends NodeJS.EventEmitter { - // from fs.FSWatcher - close(): void; - - /** - * events.EventEmitter - * 1. change - * 2. error - */ - addListener(event: string, listener: Function): this; - addListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - addListener(event: "error", listener: (code: number, signal: string) => void): this; - - on(event: string, listener: Function): this; - on(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - on(event: "error", listener: (code: number, signal: string) => void): this; - - once(event: string, listener: Function): this; - once(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - once(event: "error", listener: (code: number, signal: string) => void): this; - - prependListener(event: string, listener: Function): this; - prependListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - prependListener(event: "error", listener: (code: number, signal: string) => void): this; - - prependOnceListener(event: string, listener: Function): this; - prependOnceListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - prependOnceListener(event: "error", listener: (code: number, signal: string) => void): this; - - // From chokidar FSWatcher - - /** - * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one - * string. - */ - add(paths: string | string[]): void; - - /** - * Stop watching files, directories, or glob patterns. Takes an array of strings or just one - * string. - */ - unwatch(paths: string | string[]): void; - - /** - * Returns an object representing all the paths on the file system being watched by this - * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless - * the `cwd` option was used), and the values are arrays of the names of the items contained in - * each directory. - */ - getWatched(): IDictionary; - - /** - * Removes all listeners from watched files. - */ - close(): void; -} - -interface ILiveSyncProcessInfo { - timer: NodeJS.Timer; - watcherInfo: { - watcher: IFSWatcher, - patterns: string[] - }; - actionsChain: Promise; - isStopped: boolean; - deviceDescriptors: ILiveSyncDeviceInfo[]; - currentSyncAction: Promise; - syncToPreviewApp: boolean; -} - -interface IOptionalOutputPath { - /** - * Path where the build result is located (directory containing .ipa, .apk or .zip). - * This is required for initial checks where LiveSync will skip the rebuild in case there's already a build result and no change requiring rebuild is made since then. - * In case it is not passed, the default output for local builds will be used. - */ - outputPath?: string; -} - -/** - * Describes action used whenever building a project. - */ -interface IBuildAction { - /** - * @returns {Promise} Path to build artifact (.ipa, .apk or .zip). - */ - (): Promise; -} - -/** - * Describes options that can be passed in order to specify the exact location of the built package. - */ -interface IOutputDirectoryOptions extends IPlatform { - /** - * Directory where the project is located. - */ - projectDir: string; - - /** - * Whether the build is for emulator or not. - */ - emulator?: boolean; -} - -/** - * Describes information for LiveSync on a device. - */ -interface ILiveSyncDeviceInfo extends IOptionalOutputPath, IOptionalDebuggingOptions { - /** - * Device identifier. - */ - identifier: string; - - /** - * Action that will rebuild the application. The action must return a Promise, which is resolved with at path to build artifact. - */ - buildAction: IBuildAction; - - /** - * Whether to skip preparing the native platform. - */ - skipNativePrepare?: boolean; - - /** - * Whether debugging has been enabled for this device or not - */ - debugggingEnabled?: boolean; - - /** - * Describes options specific for each platform, like provision for iOS, target sdk for Android, etc. - */ - platformSpecificOptions?: IPlatformOptions; -} - -interface IOptionalSkipWatcher { - /** - * Defines if the watcher should be skipped. If not passed, fs.Watcher will be started. - */ - skipWatcher?: boolean; -} - -/** - * Describes a LiveSync operation. - */ -interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { - /** - * Defines if all project files should be watched for changes. In case it is not passed, only `app` dir of the project will be watched for changes. - * In case it is set to true, the package.json of the project and node_modules directory will also be watched, so any change there will be transferred to device(s). - */ - watchAllFiles?: boolean; - - /** - * Forces a build before the initial livesync. - */ - clean?: boolean; - - /** - * Defines if initial sync will be forced. - * In case it is true, transfers all project's directory on device - * In case it is false, transfers only changed files. - */ - force?: boolean; - - /** - * Defines the timeout in seconds {N} CLI will wait to find the inspector socket port from device's logs. - * If not provided, defaults to 10seconds. - */ - timeout?: string; -} - -interface IHasSyncToPreviewAppOption { - /** - * Defines if the livesync should be executed in preview app on device. - */ - syncToPreviewApp?: boolean; -} - -interface IHasUseHotModuleReloadOption { - /** - * Defines if the hot module reload should be used. - */ - useHotModuleReload: boolean; -} - -interface ILiveSyncEventData { - deviceIdentifier: string, - applicationIdentifier?: string, - projectDir: string, - syncedFiles?: string[], - error?: Error, - notification?: string, - isFullSync?: boolean -} - -interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } - -interface IIsEmulator { - isEmulator: boolean; -} - -interface ILiveSyncBuildInfo extends IIsEmulator, IPlatform { - pathToBuildItem: string; -} - -interface IProjectDataComposition { - projectData: IProjectData; -} - -/** - * Desribes object that can be passed to ensureLatestAppPackageIsInstalledOnDevice method. - */ -interface IEnsureLatestAppPackageIsInstalledOnDeviceOptions extends IProjectDataComposition, IEnvOptions, IBundle, IRelease, ISkipNativeCheckOptional, IOptionalFilesToRemove, IOptionalFilesToSync { - device: Mobile.IDevice; - preparedPlatforms: string[]; - rebuiltInformation: ILiveSyncBuildInfo[]; - deviceBuildInfoDescriptor: ILiveSyncDeviceInfo; - settings: ILatestAppPackageInstalledSettings; - liveSyncData?: ILiveSyncInfo; - modifiedFiles?: string[]; -} - -/** - * Describes the action that has been executed during ensureLatestAppPackageIsInstalledOnDevice execution. - */ -interface IAppInstalledOnDeviceResult { - /** - * Defines if the app has been installed on device from the ensureLatestAppPackageIsInstalledOnDevice method. - */ - appInstalled: boolean; -} - -/** - * Describes LiveSync operations. - */ -interface ILiveSyncService { - /** - * Starts LiveSync operation by rebuilding the application if necessary and starting watcher. - * @param {ILiveSyncDeviceInfo[]} deviceDescriptors Describes each device for which we would like to sync the application - identifier, outputPath and action to rebuild the app. - * @param {ILiveSyncInfo} liveSyncData Describes the LiveSync operation - for which project directory is the operation and other settings. - * @returns {Promise} - */ - liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; - - /** - * Starts LiveSync operation to Preview app. - * @param {IPreviewAppLiveSyncData} data Describes information about the current operation. - * @returns {Promise} Data of the QR code that should be used to start the LiveSync operation. - */ - liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise; - - /** - * Stops LiveSync operation for specified directory. - * @param {string} projectDir The directory for which to stop the operation. - * @param {string[]} @optional deviceIdentifiers Device ids for which to stop the application. In case nothing is passed, LiveSync operation will be stopped for all devices. - * @param { shouldAwaitAllActions: boolean } @optional stopOptions Specifies whether we should await all actions. - * @returns {Promise} - */ - stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; - - /** - * Returns the device information for current LiveSync operation of specified project. - * In case LiveSync has been started on many devices, but stopped for some of them at a later point, - * calling the method after that will return information only for devices for which LiveSync operation is in progress. - * @param {string} projectDir The path to project for which the LiveSync operation is executed - * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. - */ - getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; -} - -/** - * Describes LiveSync operations while debuggging. - */ -interface IDebugLiveSyncService extends ILiveSyncService { - /** - * Method used to retrieve the glob patterns which CLI will watch for file changes. Defaults to the whole app directory. - * @param {ILiveSyncInfo} liveSyncData Information needed for livesync - for example if bundle is passed or if a release build should be performed. - * @param {IProjectData} projectData Project data. - * @param {string[]} platforms Platforms to start the watcher for. - * @returns {Promise} The glob patterns. - */ - getWatcherPatterns(liveSyncData: ILiveSyncInfo, projectData: IProjectData, platforms: string[]): Promise; - - /** - * Prints debug information. - * @param {IDebugInformation} debugInformation Information to be printed. - * @returns {IDebugInformation} Full url and port where the frontend client can be connected. - */ - printDebugInformation(debugInformation: IDebugInformation): IDebugInformation; - - /** - * Enables debugging for the specified devices - * @param {IEnableDebuggingDeviceOptions[]} deviceOpts Settings used for enabling debugging for each device. - * @param {IDebuggingAdditionalOptions} enableDebuggingOptions Settings used for enabling debugging. - * @returns {Promise[]} Array of promises for each device. - */ - enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; - - /** - * Disables debugging for the specified devices - * @param {IDisableDebuggingDeviceOptions[]} deviceOptions Settings used for disabling debugging for each device. - * @param {IDebuggingAdditionalOptions} debuggingAdditionalOptions Settings used for disabling debugging. - * @returns {Promise[]} Array of promises for each device. - */ - disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[]; - - /** - * Attaches a debugger to the specified device. - * @param {IAttachDebuggerOptions} settings Settings used for controling the attaching process. - * @returns {Promise} Full url and port where the frontend client can be connected. - */ - attachDebugger(settings: IAttachDebuggerOptions): Promise; -} - -/** - * Describes additional debugging settings. - */ -interface IDebuggingAdditionalOptions extends IProjectDir { } - -/** - * Describes settings used when disabling debugging. - */ -interface IDisableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier { } - -interface IOptionalDebuggingOptions { - /** - * Optional debug options - can be used to control the start of a debug process. - */ - debugOptions?: IDebugOptions; -} - -/** - * Describes settings used when enabling debugging. - */ -interface IEnableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier, IOptionalDebuggingOptions { } - -/** - * Describes settings passed to livesync service in order to control event emitting during refresh application. - */ -interface IRefreshApplicationSettings { - shouldSkipEmitLiveSyncNotification: boolean; - shouldCheckDeveloperDiscImage: boolean; -} - -/** - * Describes settings used for attaching a debugger. - */ -interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath { -} - -interface IConnectTimeoutOption { - /** - * Time to wait for successful connection. Defaults to 30000 miliseconds. - */ - connectTimeout?: number; -} - -interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { - filesToRemove: string[]; - filesToSync: string[]; - isReinstalled: boolean; - syncAllFiles: boolean; - liveSyncDeviceInfo: ILiveSyncDeviceInfo; - hmrData: IPlatformHmrData; - force?: boolean; -} - -interface ILiveSyncResultInfo extends IHasUseHotModuleReloadOption { - modifiedFilesData: Mobile.ILocalToDevicePathData[]; - isFullSync: boolean; - waitForDebugger?: boolean; - deviceAppData: Mobile.IDeviceAppData; - didRecover?: boolean -} - -interface IAndroidLiveSyncResultInfo extends ILiveSyncResultInfo, IAndroidLivesyncSyncOperationResult { } - -interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { - device: Mobile.IDevice; - watch: boolean; - syncAllFiles: boolean; - liveSyncDeviceInfo: ILiveSyncDeviceInfo; - force?: boolean; -} - -interface IPlatformHmrData { - hash: string; - fallbackFiles: string[]; -} - -interface ITransferFilesOptions { - isFullSync: boolean; - force?: boolean; -} - -interface IPlatformLiveSyncService { - fullSync(syncInfo: IFullSyncInfo): Promise; - liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; - tryRefreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; - restartApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; - shouldRestart(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; - getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService; -} -interface IRestartApplicationInfo { - didRestart: boolean; -} - -interface INativeScriptDeviceLiveSyncService { - /** - * Specifies some action that will be executed before every sync operation - */ - beforeLiveSyncAction?(deviceAppData: Mobile.IDeviceAppData): Promise; - - /** - * Tries to refresh the application's content on the device - */ - tryRefreshApplication(projectData: IProjectData, - liveSyncInfo: ILiveSyncResultInfo): Promise; - - /** - * Restarts the specified application - */ - restartApplication(projectData: IProjectData, - liveSyncInfo: ILiveSyncResultInfo): Promise; - - /** - * Returns if the application have to be restarted in order to apply the specified livesync changes - */ - shouldRestart(projectData: IProjectData, - liveSyncInfo: ILiveSyncResultInfo): Promise; - - /** - * Removes specified files from a connected device - * @param {Mobile.IDeviceAppData} deviceAppData Data about device and app. - * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. - * @param {string} projectFilesPath The Path to the app folder inside platforms folder - * @return {Promise} - */ - removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath?: string): Promise; - - /** - * Transfers specified files to a connected device - * @param {Mobile.IDeviceAppData} deviceAppData Data about device and app. - * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. - * @param {string} projectFilesPath The Path to the app folder inside platforms folder - * @param {boolean} isFullSync Indicates if the operation is part of a fullSync - * @return {Promise} Returns the ILocalToDevicePathData of all transfered files - */ - transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise; -} - -interface IAndroidNativeScriptDeviceLiveSyncService extends INativeScriptDeviceLiveSyncService { - /** - * Guarantees all remove/update operations have finished - * @param {ILiveSyncResultInfo} liveSyncInfo Describes the LiveSync operation - for which project directory is the operation and other settings. - * @return {Promise} - */ - finalizeSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise; -} - -interface ILiveSyncSocket extends INetSocket { - uid: string, - writeAsync(data: Buffer): Promise -} - -interface IAndroidLivesyncTool { - /** - * The protocol version the current app(adnroid runtime) is using. - */ - protocolVersion: string; - - /** - * Creates new socket connection. - * @param configuration - The configuration to the socket connection. - * @returns {Promise} - */ - connect(configuration: IAndroidLivesyncToolConfiguration): Promise; - /** - * Sends a file through the socket. - * @param filePath - The full path to the file. - * @returns {Promise} - */ - sendFile(filePath: string): Promise; - /** - * Sends files through the socket. - * @param filePaths - Array of files that will be send by the socket. - * @returns {Promise} - */ - sendFiles(filePaths: string[]): Promise; - /** - * Sends all files from directory by the socket. - * @param directoryPath - The path to the directory which files will be send by the socket. - * @returns {Promise} - */ - sendDirectory(directoryPath: string): Promise; - /** - * Removes file - * @param filePath - The full path to the file. - * @returns {Promise} - */ - removeFile(filePath: string): Promise; - /** - * Removes files - * @param filePaths - Array of files that will be removed. - * @returns {Promise} - */ - removeFiles(filePaths: string[]): Promise; - /** - * Sends doSyncOperation that will be handled by the runtime. - * @param options - * @returns {Promise} - */ - sendDoSyncOperation(options?: IDoSyncOperationOptions): Promise; - /** - * Generates new operation identifier. - */ - generateOperationIdentifier(): string; - /** - * Checks if the current operation is in progress. - * @param operationId - The identifier of the operation. - */ - isOperationInProgress(operationId: string): boolean; - - /** - * Closes the current socket connection. - * @param error - Optional error for rejecting pending sync operations - */ - end(error?: Error): void; - - /** - * Returns true if a connection has been already established - */ - hasConnection(): boolean; -} - -/** - * doRefresh - Indicates if the application should be refreshed. Defaults to true. - * operationId - The identifier of the operation - * timeout - The timeout in milliseconds - */ -interface IDoSyncOperationOptions { - doRefresh?: boolean, - timeout?: number, - operationId?: string -} - -interface IAndroidLivesyncToolConfiguration extends IConnectTimeoutOption { - /** - * The application identifier. - */ - appIdentifier: string; - /** - * The device identifier. - */ - deviceIdentifier: string; - /** - * The path to app folder inside platforms folder: platforms/android/app/src/main/assets/app/ - */ - appPlatformsPath: string; - /** - * If not provided, defaults to 127.0.0.1 - */ - localHostAddress?: string; - /** - * If provider will call it when an error occurs. - */ - errorHandler?: any; -} - -interface IAndroidLivesyncSyncOperationResult { - operationId: string, - didRefresh: boolean -} - -interface IDeviceProjectRootOptions { - appIdentifier: string; - getDirname?: boolean; - syncAllFiles?: boolean; - watch?: boolean; -} - -interface IDevicePathProvider { - getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise; - getDeviceSyncZipPath(device: Mobile.IDevice): string; -} - -/** - * Describes additional options, that can be passed to LiveSyncCommandHelper. - */ -interface ILiveSyncCommandHelperAdditionalOptions extends IBuildPlatformAction, INativePrepare, IHasSyncToPreviewAppOption { - /** - * A map representing devices which have debugging enabled initially. - */ - deviceDebugMap?: IDictionary; - - /** - * Returns the path to the directory where the build output may be found. - * @param {IOutputDirectoryOptions} options Options that are used to determine the build output directory. - * @returns {string} The build output directory. - */ - getOutputDirectory?(options: IOutputDirectoryOptions): string; -} - -interface ILiveSyncCommandHelper { - /** - * Method sets up configuration, before calling livesync and expects that devices are already discovered. - * @param {Mobile.IDevice[]} devices List of discovered devices - * @param {string} platform The platform for which the livesync will be ran - * @param {ILiveSyncCommandHelperAdditionalOptions} additionalOptions @optional Additional options to control LiveSync. - * @returns {Promise} - */ - executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; - getPlatformsForOperation(platform: string): string[]; - - /** - * Validates the given platform's data - bundle identifier, etc. - * @param {string} platform The platform to be validated. - * @return {Promise} - */ - validatePlatform(platform: string): Promise>; - - /** - * Executes livesync operation. Meant to be called from within a command. - * @param {string} platform @optional platform for whith to run the livesync operation - * @param {ILiveSyncCommandHelperAdditionalOptions} additionalOptions @optional Additional options to control LiveSync. - * @returns {Promise} - */ - executeCommandLiveSync(platform?: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; -} +import { EventEmitter } from "events"; + +declare global { + // This interface is a mashup of NodeJS' along with Chokidar's event watchers + interface IFSWatcher extends NodeJS.EventEmitter { + // from fs.FSWatcher + close(): void; + + /** + * events.EventEmitter + * 1. change + * 2. error + */ + addListener(event: string, listener: Function): this; + addListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + addListener(event: "error", listener: (code: number, signal: string) => void): this; + + on(event: string, listener: Function): this; + on(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + on(event: "error", listener: (code: number, signal: string) => void): this; + + once(event: string, listener: Function): this; + once(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + once(event: "error", listener: (code: number, signal: string) => void): this; + + prependListener(event: string, listener: Function): this; + prependListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + prependListener(event: "error", listener: (code: number, signal: string) => void): this; + + prependOnceListener(event: string, listener: Function): this; + prependOnceListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; + prependOnceListener(event: "error", listener: (code: number, signal: string) => void): this; + + // From chokidar FSWatcher + + /** + * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one + * string. + */ + add(paths: string | string[]): void; + + /** + * Stop watching files, directories, or glob patterns. Takes an array of strings or just one + * string. + */ + unwatch(paths: string | string[]): void; + + /** + * Returns an object representing all the paths on the file system being watched by this + * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless + * the `cwd` option was used), and the values are arrays of the names of the items contained in + * each directory. + */ + getWatched(): IDictionary; + + /** + * Removes all listeners from watched files. + */ + close(): void; + } + + interface ILiveSyncProcessInfo { + timer: NodeJS.Timer; + watcherInfo: { + watcher: IFSWatcher, + patterns: string[] + }; + actionsChain: Promise; + isStopped: boolean; + deviceDescriptors: ILiveSyncDeviceInfo[]; + currentSyncAction: Promise; + syncToPreviewApp: boolean; + } + + interface IOptionalOutputPath { + /** + * Path where the build result is located (directory containing .ipa, .apk or .zip). + * This is required for initial checks where LiveSync will skip the rebuild in case there's already a build result and no change requiring rebuild is made since then. + * In case it is not passed, the default output for local builds will be used. + */ + outputPath?: string; + } + + /** + * Describes action used whenever building a project. + */ + interface IBuildAction { + /** + * @returns {Promise} Path to build artifact (.ipa, .apk or .zip). + */ + (): Promise; + } + + /** + * Describes options that can be passed in order to specify the exact location of the built package. + */ + interface IOutputDirectoryOptions extends IPlatform { + /** + * Directory where the project is located. + */ + projectDir: string; + + /** + * Whether the build is for emulator or not. + */ + emulator?: boolean; + } + + /** + * Describes information for LiveSync on a device. + */ + interface ILiveSyncDeviceInfo extends IOptionalOutputPath, IOptionalDebuggingOptions { + /** + * Device identifier. + */ + identifier: string; + + /** + * Action that will rebuild the application. The action must return a Promise, which is resolved with at path to build artifact. + */ + buildAction: IBuildAction; + + /** + * Whether to skip preparing the native platform. + */ + skipNativePrepare?: boolean; + + /** + * Whether debugging has been enabled for this device or not + */ + debugggingEnabled?: boolean; + + /** + * Describes options specific for each platform, like provision for iOS, target sdk for Android, etc. + */ + platformSpecificOptions?: IPlatformOptions; + } + + interface IOptionalSkipWatcher { + /** + * Defines if the watcher should be skipped. If not passed, fs.Watcher will be started. + */ + skipWatcher?: boolean; + } + + /** + * Describes a LiveSync operation. + */ + interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { + /** + * Defines if all project files should be watched for changes. In case it is not passed, only `app` dir of the project will be watched for changes. + * In case it is set to true, the package.json of the project and node_modules directory will also be watched, so any change there will be transferred to device(s). + */ + watchAllFiles?: boolean; + + /** + * Forces a build before the initial livesync. + */ + clean?: boolean; + + /** + * Defines if initial sync will be forced. + * In case it is true, transfers all project's directory on device + * In case it is false, transfers only changed files. + */ + force?: boolean; + + /** + * Defines the timeout in seconds {N} CLI will wait to find the inspector socket port from device's logs. + * If not provided, defaults to 10seconds. + */ + timeout?: string; + } + + interface IHasSyncToPreviewAppOption { + /** + * Defines if the livesync should be executed in preview app on device. + */ + syncToPreviewApp?: boolean; + } + + interface IHasUseHotModuleReloadOption { + /** + * Defines if the hot module reload should be used. + */ + useHotModuleReload: boolean; + } + + interface ILiveSyncEventData { + deviceIdentifier: string, + applicationIdentifier?: string, + projectDir: string, + syncedFiles?: string[], + error?: Error, + notification?: string, + isFullSync?: boolean + } + + interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } + + interface IIsEmulator { + isEmulator: boolean; + } + + interface ILiveSyncBuildInfo extends IIsEmulator, IPlatform { + pathToBuildItem: string; + } + + interface IProjectDataComposition { + projectData: IProjectData; + } + + /** + * Desribes object that can be passed to ensureLatestAppPackageIsInstalledOnDevice method. + */ + interface IEnsureLatestAppPackageIsInstalledOnDeviceOptions extends IProjectDataComposition, IEnvOptions, IBundle, IRelease, ISkipNativeCheckOptional, IOptionalFilesToRemove, IOptionalFilesToSync { + device: Mobile.IDevice; + preparedPlatforms: string[]; + rebuiltInformation: ILiveSyncBuildInfo[]; + deviceBuildInfoDescriptor: ILiveSyncDeviceInfo; + settings: ILatestAppPackageInstalledSettings; + liveSyncData?: ILiveSyncInfo; + modifiedFiles?: string[]; + } + + /** + * Describes the action that has been executed during ensureLatestAppPackageIsInstalledOnDevice execution. + */ + interface IAppInstalledOnDeviceResult { + /** + * Defines if the app has been installed on device from the ensureLatestAppPackageIsInstalledOnDevice method. + */ + appInstalled: boolean; + } + + /** + * Describes LiveSync operations. + */ + interface ILiveSyncService extends EventEmitter { + /** + * Starts LiveSync operation by rebuilding the application if necessary and starting watcher. + * @param {ILiveSyncDeviceInfo[]} deviceDescriptors Describes each device for which we would like to sync the application - identifier, outputPath and action to rebuild the app. + * @param {ILiveSyncInfo} liveSyncData Describes the LiveSync operation - for which project directory is the operation and other settings. + * @returns {Promise} + */ + liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; + + /** + * Starts LiveSync operation to Preview app. + * @param {IPreviewAppLiveSyncData} data Describes information about the current operation. + * @returns {Promise} Data of the QR code that should be used to start the LiveSync operation. + */ + liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise; + + /** + * Stops LiveSync operation for specified directory. + * @param {string} projectDir The directory for which to stop the operation. + * @param {string[]} @optional deviceIdentifiers Device ids for which to stop the application. In case nothing is passed, LiveSync operation will be stopped for all devices. + * @param { shouldAwaitAllActions: boolean } @optional stopOptions Specifies whether we should await all actions. + * @returns {Promise} + */ + stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; + + /** + * Returns the device information for current LiveSync operation of specified project. + * In case LiveSync has been started on many devices, but stopped for some of them at a later point, + * calling the method after that will return information only for devices for which LiveSync operation is in progress. + * @param {string} projectDir The path to project for which the LiveSync operation is executed + * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. + */ + getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; + } + + /** + * Describes LiveSync operations while debuggging. + */ + interface IDebugLiveSyncService extends ILiveSyncService { + /** + * Method used to retrieve the glob patterns which CLI will watch for file changes. Defaults to the whole app directory. + * @param {ILiveSyncInfo} liveSyncData Information needed for livesync - for example if bundle is passed or if a release build should be performed. + * @param {IProjectData} projectData Project data. + * @param {string[]} platforms Platforms to start the watcher for. + * @returns {Promise} The glob patterns. + */ + getWatcherPatterns(liveSyncData: ILiveSyncInfo, projectData: IProjectData, platforms: string[]): Promise; + + /** + * Prints debug information. + * @param {IDebugInformation} debugInformation Information to be printed. + * @returns {IDebugInformation} Full url and port where the frontend client can be connected. + */ + printDebugInformation(debugInformation: IDebugInformation): IDebugInformation; + + /** + * Enables debugging for the specified devices + * @param {IEnableDebuggingDeviceOptions[]} deviceOpts Settings used for enabling debugging for each device. + * @param {IDebuggingAdditionalOptions} enableDebuggingOptions Settings used for enabling debugging. + * @returns {Promise[]} Array of promises for each device. + */ + enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; + + /** + * Disables debugging for the specified devices + * @param {IDisableDebuggingDeviceOptions[]} deviceOptions Settings used for disabling debugging for each device. + * @param {IDebuggingAdditionalOptions} debuggingAdditionalOptions Settings used for disabling debugging. + * @returns {Promise[]} Array of promises for each device. + */ + disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[]; + + /** + * Attaches a debugger to the specified device. + * @param {IAttachDebuggerOptions} settings Settings used for controling the attaching process. + * @returns {Promise} Full url and port where the frontend client can be connected. + */ + attachDebugger(settings: IAttachDebuggerOptions): Promise; + } + + /** + * Describes additional debugging settings. + */ + interface IDebuggingAdditionalOptions extends IProjectDir { } + + /** + * Describes settings used when disabling debugging. + */ + interface IDisableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier { } + + interface IOptionalDebuggingOptions { + /** + * Optional debug options - can be used to control the start of a debug process. + */ + debugOptions?: IDebugOptions; + } + + /** + * Describes settings used when enabling debugging. + */ + interface IEnableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier, IOptionalDebuggingOptions { } + + /** + * Describes settings passed to livesync service in order to control event emitting during refresh application. + */ + interface IRefreshApplicationSettings { + shouldSkipEmitLiveSyncNotification: boolean; + shouldCheckDeveloperDiscImage: boolean; + } + + /** + * Describes settings used for attaching a debugger. + */ + interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath { + } + + interface IConnectTimeoutOption { + /** + * Time to wait for successful connection. Defaults to 30000 miliseconds. + */ + connectTimeout?: number; + } + + interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { + filesToRemove: string[]; + filesToSync: string[]; + isReinstalled: boolean; + syncAllFiles: boolean; + liveSyncDeviceInfo: ILiveSyncDeviceInfo; + hmrData: IPlatformHmrData; + force?: boolean; + } + + interface ILiveSyncResultInfo extends IHasUseHotModuleReloadOption { + modifiedFilesData: Mobile.ILocalToDevicePathData[]; + isFullSync: boolean; + waitForDebugger?: boolean; + deviceAppData: Mobile.IDeviceAppData; + didRecover?: boolean + } + + interface IAndroidLiveSyncResultInfo extends ILiveSyncResultInfo, IAndroidLivesyncSyncOperationResult { } + + interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { + device: Mobile.IDevice; + watch: boolean; + syncAllFiles: boolean; + liveSyncDeviceInfo: ILiveSyncDeviceInfo; + force?: boolean; + } + + interface IPlatformHmrData { + hash: string; + fallbackFiles: string[]; + } + + interface ITransferFilesOptions { + isFullSync: boolean; + force?: boolean; + } + + interface IPlatformLiveSyncService { + fullSync(syncInfo: IFullSyncInfo): Promise; + liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; + tryRefreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; + restartApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; + shouldRestart(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; + getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService; + } + interface IRestartApplicationInfo { + didRestart: boolean; + } + + interface INativeScriptDeviceLiveSyncService { + /** + * Specifies some action that will be executed before every sync operation + */ + beforeLiveSyncAction?(deviceAppData: Mobile.IDeviceAppData): Promise; + + /** + * Tries to refresh the application's content on the device + */ + tryRefreshApplication(projectData: IProjectData, + liveSyncInfo: ILiveSyncResultInfo): Promise; + + /** + * Restarts the specified application + */ + restartApplication(projectData: IProjectData, + liveSyncInfo: ILiveSyncResultInfo): Promise; + + /** + * Returns if the application have to be restarted in order to apply the specified livesync changes + */ + shouldRestart(projectData: IProjectData, + liveSyncInfo: ILiveSyncResultInfo): Promise; + + /** + * Removes specified files from a connected device + * @param {Mobile.IDeviceAppData} deviceAppData Data about device and app. + * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. + * @param {string} projectFilesPath The Path to the app folder inside platforms folder + * @return {Promise} + */ + removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath?: string): Promise; + + /** + * Transfers specified files to a connected device + * @param {Mobile.IDeviceAppData} deviceAppData Data about device and app. + * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. + * @param {string} projectFilesPath The Path to the app folder inside platforms folder + * @param {boolean} isFullSync Indicates if the operation is part of a fullSync + * @return {Promise} Returns the ILocalToDevicePathData of all transfered files + */ + transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise; + } + + interface IAndroidNativeScriptDeviceLiveSyncService extends INativeScriptDeviceLiveSyncService { + /** + * Guarantees all remove/update operations have finished + * @param {ILiveSyncResultInfo} liveSyncInfo Describes the LiveSync operation - for which project directory is the operation and other settings. + * @return {Promise} + */ + finalizeSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise; + } + + interface ILiveSyncSocket extends INetSocket { + uid: string, + writeAsync(data: Buffer): Promise + } + + interface IAndroidLivesyncTool { + /** + * The protocol version the current app(adnroid runtime) is using. + */ + protocolVersion: string; + + /** + * Creates new socket connection. + * @param configuration - The configuration to the socket connection. + * @returns {Promise} + */ + connect(configuration: IAndroidLivesyncToolConfiguration): Promise; + /** + * Sends a file through the socket. + * @param filePath - The full path to the file. + * @returns {Promise} + */ + sendFile(filePath: string): Promise; + /** + * Sends files through the socket. + * @param filePaths - Array of files that will be send by the socket. + * @returns {Promise} + */ + sendFiles(filePaths: string[]): Promise; + /** + * Sends all files from directory by the socket. + * @param directoryPath - The path to the directory which files will be send by the socket. + * @returns {Promise} + */ + sendDirectory(directoryPath: string): Promise; + /** + * Removes file + * @param filePath - The full path to the file. + * @returns {Promise} + */ + removeFile(filePath: string): Promise; + /** + * Removes files + * @param filePaths - Array of files that will be removed. + * @returns {Promise} + */ + removeFiles(filePaths: string[]): Promise; + /** + * Sends doSyncOperation that will be handled by the runtime. + * @param options + * @returns {Promise} + */ + sendDoSyncOperation(options?: IDoSyncOperationOptions): Promise; + /** + * Generates new operation identifier. + */ + generateOperationIdentifier(): string; + /** + * Checks if the current operation is in progress. + * @param operationId - The identifier of the operation. + */ + isOperationInProgress(operationId: string): boolean; + + /** + * Closes the current socket connection. + * @param error - Optional error for rejecting pending sync operations + */ + end(error?: Error): void; + + /** + * Returns true if a connection has been already established + */ + hasConnection(): boolean; + } + + /** + * doRefresh - Indicates if the application should be refreshed. Defaults to true. + * operationId - The identifier of the operation + * timeout - The timeout in milliseconds + */ + interface IDoSyncOperationOptions { + doRefresh?: boolean, + timeout?: number, + operationId?: string + } + + interface IAndroidLivesyncToolConfiguration extends IConnectTimeoutOption { + /** + * The application identifier. + */ + appIdentifier: string; + /** + * The device identifier. + */ + deviceIdentifier: string; + /** + * The path to app folder inside platforms folder: platforms/android/app/src/main/assets/app/ + */ + appPlatformsPath: string; + /** + * If not provided, defaults to 127.0.0.1 + */ + localHostAddress?: string; + /** + * If provider will call it when an error occurs. + */ + errorHandler?: any; + } + + interface IAndroidLivesyncSyncOperationResult { + operationId: string, + didRefresh: boolean + } + + interface IDeviceProjectRootOptions { + appIdentifier: string; + getDirname?: boolean; + syncAllFiles?: boolean; + watch?: boolean; + } + + interface IDevicePathProvider { + getDeviceProjectRootPath(device: Mobile.IDevice, options: IDeviceProjectRootOptions): Promise; + getDeviceSyncZipPath(device: Mobile.IDevice): string; + } + + /** + * Describes additional options, that can be passed to LiveSyncCommandHelper. + */ + interface ILiveSyncCommandHelperAdditionalOptions extends IBuildPlatformAction, INativePrepare, IHasSyncToPreviewAppOption { + /** + * A map representing devices which have debugging enabled initially. + */ + deviceDebugMap?: IDictionary; + + /** + * Returns the path to the directory where the build output may be found. + * @param {IOutputDirectoryOptions} options Options that are used to determine the build output directory. + * @returns {string} The build output directory. + */ + getOutputDirectory?(options: IOutputDirectoryOptions): string; + } + + interface ILiveSyncCommandHelper { + /** + * Method sets up configuration, before calling livesync and expects that devices are already discovered. + * @param {Mobile.IDevice[]} devices List of discovered devices + * @param {string} platform The platform for which the livesync will be ran + * @param {ILiveSyncCommandHelperAdditionalOptions} additionalOptions @optional Additional options to control LiveSync. + * @returns {Promise} + */ + executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; + getPlatformsForOperation(platform: string): string[]; + + /** + * Validates the given platform's data - bundle identifier, etc. + * @param {string} platform The platform to be validated. + * @return {Promise} + */ + validatePlatform(platform: string): Promise>; + + /** + * Executes livesync operation. Meant to be called from within a command. + * @param {string} platform @optional platform for whith to run the livesync operation + * @param {ILiveSyncCommandHelperAdditionalOptions} additionalOptions @optional Additional options to control LiveSync. + * @returns {Promise} + */ + executeCommandLiveSync(platform?: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; + } + +} \ No newline at end of file diff --git a/lib/definitions/preview-app-livesync.d.ts b/lib/definitions/preview-app-livesync.d.ts index bf728df915..4eef7d8cc7 100644 --- a/lib/definitions/preview-app-livesync.d.ts +++ b/lib/definitions/preview-app-livesync.d.ts @@ -21,12 +21,14 @@ declare global { interface IPreviewAppLiveSyncData extends IProjectDir, IHasUseHotModuleReloadOption, IBundle, IEnvOptions { } interface IPreviewSdkService extends EventEmitter { - getQrCodeUrl(options: IHasUseHotModuleReloadOption): string; + getQrCodeUrl(options: IGetQrCodeUrlOptions): string; initialize(getInitialFiles: (device: Device) => Promise): void; applyChanges(filesPayload: FilesPayload): Promise; stop(): void; } + interface IGetQrCodeUrlOptions extends IHasUseHotModuleReloadOption, IProjectDir { } + interface IPreviewAppPluginsService { getPluginsUsageWarnings(data: IPreviewAppLiveSyncData, device: Device): string[]; comparePluginsOnDevice(data: IPreviewAppLiveSyncData, device: Device): Promise; @@ -47,7 +49,7 @@ declare global { platform?: string; } - interface IPrintLiveSyncOptions extends IHasUseHotModuleReloadOption { + interface IPrintLiveSyncOptions extends IGetQrCodeUrlOptions { /** * If set to true, a link will be shown on console instead of QR code * Default value is false. diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 066c3d781c..05d67d21ab 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -75,6 +75,7 @@ interface INsConfig { appResourcesPath?: string; shared?: boolean; useLegacyWorkflow?: boolean; + previewAppSchema?: string; } interface IProjectData extends ICreateProjectData { @@ -105,6 +106,11 @@ interface IProjectData extends ICreateProjectData { */ useLegacyWorkflow: boolean; + /** + * Defines the schema for the preview app + */ + previewAppSchema: string; + /** * Initializes project data with the given project directory. If none supplied defaults to --path option or cwd. * @param {string} projectDir Project root directory. diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 1d64c300e3..9a086ed61d 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,3 +1,5 @@ +import { LiveSyncEvents } from "../constants"; + export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; @@ -120,6 +122,15 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { force: this.$options.force }; + const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); + this.$liveSyncService.on(LiveSyncEvents.liveSyncStopped, (data: { projectDir: string, deviceIdentifier: string }) => { + _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); + + if (remainingDevicesToSync.length === 0) { + process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); + } + }); + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } diff --git a/lib/project-data.ts b/lib/project-data.ts index 1039624b98..328d8a6198 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -62,6 +62,7 @@ export class ProjectData implements IProjectData { public podfilePath: string; public isShared: boolean; public useLegacyWorkflow: boolean; + public previewAppSchema: string; constructor(private $fs: IFileSystem, private $errors: IErrors, @@ -137,6 +138,7 @@ export class ProjectData implements IProjectData { this.podfilePath = path.join(this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants.PODFILE_NAME); this.isShared = !!(this.nsConfig && this.nsConfig.shared); this.useLegacyWorkflow = this.nsConfig && this.nsConfig.useLegacyWorkflow; + this.previewAppSchema = this.nsConfig && this.nsConfig.previewAppSchema; return; } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index d150d1b417..6b7061f46b 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -3,24 +3,21 @@ import * as choki from "chokidar"; import { EOL } from "os"; import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; -import { PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME, TrackActionNames } from "../../constants"; +import { + PACKAGE_JSON_FILE_NAME, + LiveSyncTrackActionNames, + USER_INTERACTION_NEEDED_EVENT_NAME, + DEBUGGER_ATTACHED_EVENT_NAME, + DEBUGGER_DETACHED_EVENT_NAME, + TrackActionNames, + LiveSyncEvents +} from "../../constants"; import { DeviceTypes, DeviceDiscoveryEventNames, HmrConstants } from "../../common/constants"; import { cache } from "../../common/decorators"; -import { PreviewAppLiveSyncEvents } from "./playground/preview-app-constants"; import { performanceLog } from "../../common/decorators"; const deviceDescriptorPrimaryKey = "identifier"; -const LiveSyncEvents = { - liveSyncStopped: "liveSyncStopped", - // In case we name it error, EventEmitter expects instance of Error to be raised and will also raise uncaught exception in case there's no handler - liveSyncError: "liveSyncError", - previewAppLiveSyncError: PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, - liveSyncExecuted: "liveSyncExecuted", - liveSyncStarted: "liveSyncStarted", - liveSyncNotification: "notify" -}; - export class LiveSyncService extends EventEmitter implements IDebugLiveSyncService { // key is projectDir protected liveSyncProcessesInfo: IDictionary = {}; @@ -66,7 +63,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi env: data.env, }); - const url = this.$previewSdkService.getQrCodeUrl({ useHotModuleReload: data.useHotModuleReload }); + const url = this.$previewSdkService.getQrCodeUrl({ projectDir: data.projectDir, useHotModuleReload: data.useHotModuleReload }); const result = await this.$previewQrCodeService.getLiveSyncQrCode(url); return result; } diff --git a/lib/services/livesync/playground/preview-sdk-service.ts b/lib/services/livesync/playground/preview-sdk-service.ts index 5947e11632..fc9565184a 100644 --- a/lib/services/livesync/playground/preview-sdk-service.ts +++ b/lib/services/livesync/playground/preview-sdk-service.ts @@ -12,13 +12,21 @@ export class PreviewSdkService extends EventEmitter implements IPreviewSdkServic private $httpClient: Server.IHttpClient, private $logger: ILogger, private $previewDevicesService: IPreviewDevicesService, - private $previewAppLogProvider: IPreviewAppLogProvider) { + private $previewAppLogProvider: IPreviewAppLogProvider, + private $projectDataService: IProjectDataService) { super(); } - public getQrCodeUrl(options: IHasUseHotModuleReloadOption): string { - const hmrValue = options.useHotModuleReload ? "1" : "0"; - return `nsplay://boot?instanceId=${this.instanceId}&pKey=${PubnubKeys.PUBLISH_KEY}&sKey=${PubnubKeys.SUBSCRIBE_KEY}&template=play-ng&hmr=${hmrValue}`; + public getQrCodeUrl(options: IGetQrCodeUrlOptions): string { + const { projectDir, useHotModuleReload } = options; + const projectData = this.$projectDataService.getProjectData(projectDir); + const schema = projectData.previewAppSchema || "nsplay"; + // TODO: Use the correct keys for the schema + const publishKey = PubnubKeys.PUBLISH_KEY; + const subscribeKey = PubnubKeys.SUBSCRIBE_KEY; + const hmrValue = useHotModuleReload ? "1" : "0"; + const result = `${schema}://boot?instanceId=${this.instanceId}&pKey=${publishKey}&sKey=${subscribeKey}&template=play-ng&hmr=${hmrValue}`; + return result; } public async initialize(getInitialFiles: (device: Device) => Promise): Promise { diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index 4cbd86c4ad..e30353eded 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -194,7 +194,7 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ useHotModuleReload: options.hmr }); - await this.$previewQrCodeService.printLiveSyncQrCode({ useHotModuleReload: options.hmr, link: options.link }); + await this.$previewQrCodeService.printLiveSyncQrCode({ projectDir, useHotModuleReload: options.hmr, link: options.link }); } } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index ae508103b4..8c3f83c9f4 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "5.3.0", + "version": "5.3.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index aa0f51a830..6817f93334 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.3.0", + "version": "5.3.1", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { diff --git a/test/services/preview-sdk-service.ts b/test/services/preview-sdk-service.ts index 3385173648..c129ead91d 100644 --- a/test/services/preview-sdk-service.ts +++ b/test/services/preview-sdk-service.ts @@ -3,7 +3,7 @@ import { Yok } from "../../lib/common/yok"; import { assert } from "chai"; import { LoggerStub } from "../stubs"; -const getPreviewSdkService = (): IPreviewSdkService => { +const createTestInjector = (): IInjector => { const testInjector = new Yok(); testInjector.register("logger", LoggerStub); testInjector.register("config", {}); @@ -13,26 +13,65 @@ const getPreviewSdkService = (): IPreviewSdkService => { testInjector.register("httpClient", { httpRequest: async (options: any, proxySettings?: IProxySettings): Promise => undefined }); + testInjector.register("projectDataService", { + getProjectData: () => ({}) + }); - return testInjector.resolve("previewSdkService"); + return testInjector; }; describe('PreviewSdkService', () => { + let injector: IInjector, previewSdkService: IPreviewSdkService; + + beforeEach(() => { + injector = createTestInjector(); + previewSdkService = injector.resolve("previewSdkService"); + }); + describe('getQrCodeUrl', () => { - it('sets hmr to 1 when useHotModuleReload is true', async () => { - const sdk = getPreviewSdkService(); + describe("hmr", () => { + it('sets hmr to 1 when useHotModuleReload is true', async () => { + const previewUrl = previewSdkService.getQrCodeUrl({ projectDir: "", useHotModuleReload: true }); - const previewUrl = sdk.getQrCodeUrl({ useHotModuleReload: true }); + assert.isTrue(previewUrl.indexOf("hmr=1") > -1); + }); + it('sets hmr to 0 when useHotModuleReload is false', async () => { + const previewUrl = previewSdkService.getQrCodeUrl({ projectDir: "", useHotModuleReload: false }); - assert.isTrue(previewUrl.indexOf("hmr=1") > -1); + assert.isTrue(previewUrl.indexOf("hmr=0") > -1); + }); }); - }); - it('sets hmr to 0 when useHotModuleReload is false', async () => { - const sdk = getPreviewSdkService(); + describe("schema", () => { + const testCases = [ + { + name: "should return the schema from api", + schemaFromNsConfig: "nsplay", + expectedSchemaName: "nsplay" + }, + { + name: "should return the schema from nsconfig", + schemaFromNsConfig: "ksplay", + expectedSchemaName: "ksplay" + }, + { + name: "should return the default schema", + schemaFromNsConfig: null, + expectedSchemaName: "nsplay" + } + ]; + + _.each(testCases, testCase => { + it(`${testCase.name}`, () => { + const qrCodeOptions = { projectDir: "myTestDir", useHotModuleReload: true }; + const projectDataService = injector.resolve("projectDataService"); + projectDataService.getProjectData = () => ({ previewAppSchema: testCase.schemaFromNsConfig }); - const previewUrl = sdk.getQrCodeUrl({ useHotModuleReload: false }); + const qrCodeUrl = previewSdkService.getQrCodeUrl(qrCodeOptions); - assert.isTrue(previewUrl.indexOf("hmr=0") > -1); + assert.deepEqual(qrCodeUrl.split(":")[0], testCase.expectedSchemaName); + }); + }); + }); }); }); diff --git a/test/stubs.ts b/test/stubs.ts index 1ee833096d..510b604b2b 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -332,6 +332,7 @@ export class ProjectDataStub implements IProjectData { public podfilePath: string; public isShared: boolean; public useLegacyWorkflow: boolean; + public previewAppSchema: string; public initializeProjectData(projectDir?: string): void { this.projectDir = this.projectDir || projectDir; @@ -655,7 +656,7 @@ export class DebugServiceStub extends EventEmitter implements IDeviceDebugServic public platform: string; } -export class LiveSyncServiceStub implements ILiveSyncService { +export class LiveSyncServiceStub extends EventEmitter implements ILiveSyncService { public async liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise { return; }