Skip to content

fix: allow Debug + HMR using the public API #4246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,10 +473,14 @@ interface IGenerateOptions {
collection?: string;
}

interface IDebugInformation extends IPort, Mobile.IDeviceIdentifier {
interface IDebugInformation extends IPort, Mobile.IDeviceIdentifier, IHasHasReconnected {
url: string;
}

interface IHasHasReconnected {
hasReconnected: boolean;
}

interface IPort {
port: Number;
}
Expand Down
10 changes: 9 additions & 1 deletion lib/definitions/debug.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ interface IDebugOptions {
* Defines if the handshake(AppLaunching notification) between CLI and runtime should be executed. The handshake is not needed when CLI retries to attach to the debugger.
*/
skipHandshake?: boolean;
/**
* Forces the debugger attach event to be emitted.
*/
forceDebuggerAttachedEvent?: boolean;
}

/**
Expand Down Expand Up @@ -161,5 +165,9 @@ interface IDeviceDebugService extends IPlatform, NodeJS.EventEmitter {
* @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line.
* @returns {Promise<string>} Full url where the frontend client may be connected.
*/
debug(debugData: IAppDebugData, debugOptions: IDebugOptions): Promise<string>;
debug(debugData: IAppDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo>;
}

interface IDebugResultInfo extends IHasHasReconnected {
debugUrl: string;
}
13 changes: 10 additions & 3 deletions lib/definitions/livesync.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ interface ILiveSyncEventData {
applicationIdentifier?: string,
projectDir: string,
syncedFiles?: string[],
error? : Error,
error?: Error,
notification?: string,
isFullSync?: boolean
}
Expand Down Expand Up @@ -390,11 +390,18 @@ interface ITransferFilesOptions {
interface IPlatformLiveSyncService {
fullSync(syncInfo: IFullSyncInfo): Promise<ILiveSyncResultInfo>;
liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise<ILiveSyncResultInfo>;
refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void>;
refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo>;
prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir, liveSyncInfo: ILiveSyncInfo, debugOptions: IDebugOptions): Promise<void>;
getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService;
}

interface IHasDidRestart {
didRestart: boolean;
}

interface IRefreshApplicationInfo extends IHasDidRestart {
}

interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase {
/**
* Refreshes the application's content on a device
Expand All @@ -405,7 +412,7 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase
* @return {Promise<void>}
*/
refreshApplication(projectData: IProjectData,
liveSyncInfo: ILiveSyncResultInfo): Promise<void>;
liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo>;

/**
* Removes specified files from a connected device
Expand Down
19 changes: 12 additions & 7 deletions lib/services/android-device-debug-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi
this.deviceIdentifier = device.deviceInfo.identifier;
}

public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
this._packageName = debugData.applicationIdentifier;
const result = this.device.isEmulator
? await this.debugOnEmulator(debugData, debugOptions)
Expand Down Expand Up @@ -59,7 +59,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi
return this.removePortForwarding();
}

private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
private async debugOnEmulator(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
// Assure we've detected the emulator as device
// For example in case deployOnEmulator had stated new emulator instance
// we need some time to detect it. Let's force detection.
Expand Down Expand Up @@ -97,7 +97,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi
return this.device.adb.executeCommand(["forward", `tcp:${local}`, `localabstract:${remote}`]);
}

private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
private async debugOnDevice(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
let packageFile = "";

if (!debugOptions.start && !this.device.isEmulator) {
Expand All @@ -113,27 +113,32 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi
projectName
};

const action = (device: Mobile.IAndroidDevice): Promise<string> => this.debugCore(device, packageFile, appData, debugOptions);
const action = (device: Mobile.IAndroidDevice): Promise<IDebugResultInfo> => this.debugCore(device, packageFile, appData, debugOptions);

const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(this.deviceIdentifier));
return deviceActionResult[0].result;
}

private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, appData: Mobile.IApplicationData, debugOptions: IDebugOptions): Promise<string> {
private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, appData: Mobile.IApplicationData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
const result: IDebugResultInfo = { hasReconnected: false, debugUrl: null };

if (debugOptions.stop) {
await this.removePortForwarding();
return null;
return result;
}

if (!debugOptions.start) {
await this.debugStartCore(appData, debugOptions);
result.hasReconnected = true;
}

await this.validateRunningApp(device.deviceInfo.identifier, appData.appId);
const debugPort = await this.getForwardedDebugPort(device.deviceInfo.identifier, appData.appId);
await this.printDebugPort(device.deviceInfo.identifier, debugPort);

return this.getChromeDebugUrl(debugOptions, debugPort);
result.debugUrl = this.getChromeDebugUrl(debugOptions, debugPort);

return result;
}

private async printDebugPort(deviceId: string, port: number): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion lib/services/debug-service-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export abstract class DebugServiceBase extends EventEmitter implements IDeviceDe

public abstract get platform(): string;

public abstract async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string>;
public abstract async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo>;

public abstract async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise<void>;

Expand Down
21 changes: 8 additions & 13 deletions lib/services/debug-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ export class DebugService extends EventEmitter implements IDebugService {
}

const debugOptions: IDebugOptions = _.cloneDeep(options);

// TODO: Check if app is running.
// For now we can only check if app is running on Android.
// After we find a way to check on iOS we should use it here.
let result: string;

const debugService = this.getDeviceDebugService(device);
if (!debugService) {
this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`);
Expand All @@ -62,9 +56,9 @@ export class DebugService extends EventEmitter implements IDebugService {
}
}

result = await debugService.debug(debugData, debugOptions);
const debugResultInfo = await debugService.debug(debugData, debugOptions);

return this.getDebugInformation(result, device.deviceInfo.identifier);
return this.getDebugInformation(debugResultInfo, device.deviceInfo.identifier);
}

public debugStop(deviceIdentifier: string): Promise<void> {
Expand Down Expand Up @@ -100,16 +94,17 @@ export class DebugService extends EventEmitter implements IDebugService {
platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler);
}

private getDebugInformation(fullUrl: string, deviceIdentifier: string): IDebugInformation {
private getDebugInformation(debugResultInfo: IDebugResultInfo, deviceIdentifier: string): IDebugInformation {
const debugInfo: IDebugInformation = {
url: fullUrl,
url: debugResultInfo.debugUrl,
port: 0,
deviceIdentifier
deviceIdentifier,
hasReconnected: debugResultInfo.hasReconnected
};

if (fullUrl) {
if (debugResultInfo.debugUrl) {
const parseQueryString = true;
const wsQueryParam = <string>parse(fullUrl, parseQueryString).query.ws;
const wsQueryParam = <string>parse(debugResultInfo.debugUrl, parseQueryString).query.ws;
const hostPortSplit = wsQueryParam && wsQueryParam.split(":");
debugInfo.port = hostPortSplit && +hostPortSplit[1];
}
Expand Down
8 changes: 6 additions & 2 deletions lib/services/ios-device-debug-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,21 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe
return "ios";
}

public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<string> {
public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise<IDebugResultInfo> {
const result: IDebugResultInfo = { hasReconnected: false, debugUrl: null };
this.validateOptions(debugOptions);

await this.startDeviceLogProcess(debugData, debugOptions);
await this.$iOSDebuggerPortService.attachToDebuggerPortFoundEvent(this.device, debugData, debugOptions);

if (!debugOptions.start) {
await this.startApp(debugData, debugOptions);
result.hasReconnected = true;
}

return this.wireDebuggerClient(debugData, debugOptions);
result.debugUrl = await this.wireDebuggerClient(debugData, debugOptions);

return result;
}

public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise<void> {
Expand Down
10 changes: 7 additions & 3 deletions lib/services/livesync/android-device-livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa
protected device: Mobile.IAndroidDevice,
$filesHashService: IFilesHashService,
$logger: ILogger) {
super($injector, $platformsData, $filesHashService, $logger, device);
super($injector, $platformsData, $filesHashService, $logger, device);
}

public async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<void> {
Expand All @@ -26,7 +26,8 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa
await this.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath);
}

public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void> {
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo> {
const result: IRefreshApplicationInfo = { didRestart: false };
const deviceAppData = liveSyncInfo.deviceAppData;
const localToDevicePaths = liveSyncInfo.modifiedFilesData;
const deviceProjectRootDirname = await this.$devicePathProvider.getDeviceProjectRootPath(liveSyncInfo.deviceAppData.device, {
Expand All @@ -48,8 +49,11 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa
(localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(liveSyncInfo, localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform));

if (!canExecuteFastSync) {
return this.restartApplication(deviceAppData, projectData.projectName);
await this.restartApplication(deviceAppData, projectData.projectName);
result.didRestart = true;
}

return result;
}

private async cleanLivesyncDirectories(deviceAppData: Mobile.IDeviceAppData): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe
return result;
}

public async refreshApplication(projectData: IProjectData, liveSyncInfo: IAndroidLiveSyncResultInfo) {
public async refreshApplication(projectData: IProjectData, liveSyncInfo: IAndroidLiveSyncResultInfo): Promise<IRefreshApplicationInfo> {
const result: IRefreshApplicationInfo = { didRestart: false };
const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo, liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform);
if (!canExecuteFastSync || !liveSyncInfo.didRefresh) {
await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName });
Expand All @@ -103,7 +104,11 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe
this.$logger.trace("Failed to connect after app restart.");
}
}

result.didRestart = true;
}

return result;
}

public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<void> {
Expand Down
12 changes: 9 additions & 3 deletions lib/services/livesync/ios-device-livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen
await Promise.all(_.map(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), deviceAppData.appIdentifier)));
}

public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void> {
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo> {
const result: IRefreshApplicationInfo = { didRestart: false };
const deviceAppData = liveSyncInfo.deviceAppData;
const localToDevicePaths = liveSyncInfo.modifiedFilesData;
if (liveSyncInfo.isFullSync) {
await this.restartApplication(deviceAppData, projectData.projectName);
return;
result.didRestart = true;
return result;
}

let scriptRelatedFiles: Mobile.ILocalToDevicePathData[] = [];
Expand All @@ -54,14 +56,18 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen

if (!canExecuteFastSync) {
await this.restartApplication(deviceAppData, projectData.projectName);
return;
result.didRestart = true;
return result;
}

if (await this.setupSocketIfNeeded(projectData)) {
await this.reloadPage(otherFiles);
} else {
await this.restartApplication(deviceAppData, projectData.projectName);
result.didRestart = true;
}

return result;
}

private async restartApplication(deviceAppData: Mobile.IDeviceAppData, projectName: string): Promise<void> {
Expand Down
25 changes: 17 additions & 8 deletions lib/services/livesync/livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,20 +156,21 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
}
}

private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise<void | IDebugInformation> {
private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise<IRefreshApplicationInfo | IDebugInformation> {
const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir);

return deviceDescriptor && deviceDescriptor.debugggingEnabled ?
this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) :
this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath);
}

private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IShouldSkipEmitLiveSyncNotification): Promise<void> {
private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IShouldSkipEmitLiveSyncNotification): Promise<IRefreshApplicationInfo> {
let result: IRefreshApplicationInfo = { didRestart: false };
const platform = liveSyncResultInfo.deviceAppData.platform;
const platformLiveSyncService = this.getLiveSyncService(platform);
const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()];
try {
await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo);
result = await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo);
} catch (err) {
this.$logger.info(`Error while trying to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`);
const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`;
Expand All @@ -193,12 +194,14 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
isFullSync: liveSyncResultInfo.isFullSync
});

return result;
}

private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise<IDebugInformation> {
let didRestart = false;
const deviceAppData = liveSyncResultInfo.deviceAppData;
debugOptions = debugOptions || {};

const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier;
if (debugOptions.debugBrk) {
await this.$debugService.debugStop(deviceIdentifier);
Expand All @@ -211,13 +214,15 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOptions, outputPath);
}
} else {
await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true });
const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true });
didRestart = refreshInfo.didRestart;
}

// we do not stop the application when debugBrk is false, so we need to attach, instead of launch
// if we try to send the launch request, the debugger port will not be printed and the command will timeout
debugOptions.start = !debugOptions.debugBrk;

debugOptions.forceDebuggerAttachedEvent = didRestart;
const deviceOption = {
deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
debugOptions: debugOptions,
Expand Down Expand Up @@ -269,13 +274,17 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath);

const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions);
const result = this.printDebugInformation(debugInfo);
const fireDebuggerAttachedEvent = settings.debugOptions.forceDebuggerAttachedEvent || debugInfo.hasReconnected;
const result = this.printDebugInformation(debugInfo, fireDebuggerAttachedEvent);
return result;
}

public printDebugInformation(debugInformation: IDebugInformation): IDebugInformation {
public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation {
if (!!debugInformation.url) {
this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation);
if (fireDebuggerAttachedEvent) {
this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation);
}

this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan);
}

Expand Down
Loading