Adding support for 'ionic serve' in browser and 'ionic run --livereload' for Android

This commit is contained in:
Guillaume Jenkins 2016-03-08 14:12:03 -08:00
Родитель c15aa47789
Коммит b70f23a262
9 изменённых файлов: 419 добавлений и 143 удалений

9
.vscode/launch.json поставляемый
Просмотреть файл

@ -9,7 +9,7 @@
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "out/src",
"outDir": "${workspaceRoot}/out/src",
"preLaunchTask": "build"
},
{
@ -20,19 +20,20 @@
"args": ["test/testProject", "--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "out/test",
"outDir": "${workspaceRoot}/out/src",
"preLaunchTask": "prepare-integration-tests"
},
{
"name": "Launch debugger as server",
"type": "node",
"request": "launch",
"program": "./out/webkit/debugCordova.js",
"program": "${workspaceRoot}/out/src/debugger/debugCordova.js",
"runtimeArgs": ["--harmony"],
"stopOnEntry": false,
"args": [ "--server=4712" ],
"sourceMaps": true,
"outDir": "out"
"outDir": "${workspaceRoot}/out",
"preLaunchTask": "build"
}
]
}

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

@ -161,6 +161,13 @@
"port": 9220,
"sourceMaps": true,
"cwd": "${workspaceRoot}"
},
{
"name": "Run in the browser",
"type": "cordova",
"request": "launch",
"platform": "browser",
"cwd": "${workspaceRoot}"
}
],
"configurationAttributes": {
@ -222,6 +229,19 @@
"type": "number",
"description": "Timeout for individual steps when launching iOS app",
"default": 5000
},
"noLivereload": {
"type": "boolean",
"description": "Set to true to disable Ionic live reload (changes to the sources will not be pushed to the running app)",
"default": false
},
"devServerPort": {
"type": "number",
"description": "The port on which Ionic's live reload server should listen on"
},
"devServerAddress": {
"type": "string",
"description": "The IP address that Ionic should use for the live reload server"
}
}
},

9
src/debugger/cordovaAdapterInferfaces.d.ts поставляемый
Просмотреть файл

@ -5,6 +5,15 @@
interface ICordovaLaunchRequestArgs extends DebugProtocol.LaunchRequestArguments, ICordovaAttachRequestArgs {
iosDebugProxyPort?: number;
appStepLaunchTimeout?: number;
// Ionic livereload properties
noLivereload?: boolean;
devServerPort?: number;
devServerAddress?: string;
// Chrome debug properties
url?: string;
userDataDir?: string;
}
interface ICordovaAttachRequestArgs extends DebugProtocol.AttachRequestArguments, IAttachRequestArgs {

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

@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as child_process from 'child_process';
import * as elementtree from 'elementtree';
import * as fs from 'fs';
import * as http from 'http';
@ -10,20 +11,20 @@ import * as path from 'path';
import * as Q from 'q';
import {CordovaIosDeviceLauncher} from './cordovaIosDeviceLauncher';
import {cordovaRunCommand, execCommand} from './extension';
import {cordovaRunCommand, cordovaStartCommand, execCommand, killChildProcess} from './extension';
import {WebKitDebugAdapter} from '../../debugger/webkit/webKitDebugAdapter';
import {CordovaProjectHelper} from '../utils/cordovaProjectHelper';
import {TelemetryHelper} from '../utils/telemetryHelper';
import {IProjectType, TelemetryHelper} from '../utils/telemetryHelper';
import {settingsHome} from '../utils/settingsHelper'
export class CordovaDebugAdapter extends WebKitDebugAdapter {
private outputLogger: (message: string, error?: boolean) => void;
private adbPortForwardingInfo: {targetDevice: string, port: number};
private static CHROME_DATA_DIR = 'chrome_sandbox_dir'; // The directory to use for the sandboxed Chrome instance that gets launched to debug the app
public constructor(outputLogger: (message: string, error?: boolean) => void) {
private outputLogger: (message: string, error?: boolean) => void;
private adbPortForwardingInfo: { targetDevice: string, port: number };
private ionicLivereloadProcess: child_process.ChildProcess;
public constructor(outputLogger: (message: string, error?: boolean) => void) {
super();
this.outputLogger = outputLogger;
}
@ -37,23 +38,30 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
let platform = launchArgs.platform && launchArgs.platform.toLowerCase();
return TelemetryHelper.determineProjectTypes(launchArgs.cwd)
.then((projectType) => generator.add('projectType', projectType, false))
.then(() => {
this.outputLogger(`Launching for ${platform} (This may take a while)...`);
switch (platform) {
case 'android':
generator.add('platform', platform, false);
return this.launchAndroid(launchArgs);
case 'ios':
generator.add('platform', platform, false);
return this.launchIos(launchArgs);
default:
generator.add('unknownPlatform', platform, true);
throw new Error(`Unknown Platform: ${platform}`);
}
}).then(() => {
return this.attach(launchArgs);
});
.then((projectType) => {
generator.add('projectType', projectType, false);
this.outputLogger(`Launching for ${platform} (This may take a while)...`);
switch (platform) {
case 'android':
generator.add('platform', platform, false);
return this.launchAndroid(launchArgs, projectType);
case 'ios':
generator.add('platform', platform, false);
return this.launchIos(launchArgs, projectType);
case 'browser':
generator.add('platform', platform, false);
return this.launchBrowser(launchArgs, projectType);
default:
generator.add('unknownPlatform', platform, true);
throw new Error(`Unknown Platform: ${platform}`);
}
}).then(() => {
// For the browser platform, we call super.launch(), which already attaches. For other platforms, attach here
if (platform !== 'browser') {
return this.attach(launchArgs);
}
});
}).done(resolve, reject));
}
@ -65,51 +73,81 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
let platform = attachArgs.platform && attachArgs.platform.toLowerCase();
return TelemetryHelper.determineProjectTypes(attachArgs.cwd)
.then((projectType) => generator.add('projectType', projectType, false))
.then(() => {
this.outputLogger(`Attaching to ${platform}`);
switch (platform) {
case 'android':
generator.add('platform', platform, false);
return this.attachAndroid(attachArgs);
case 'ios':
generator.add('platform', platform, false);
return this.attachIos(attachArgs);
default:
generator.add('unknownPlatform', platform, true);
throw new Error(`Unknown Platform: ${platform}`);
}
}).then((processedAttachArgs: IAttachRequestArgs & {url?: string}) => {
this.outputLogger('Attaching to app.');
this.outputLogger('', true); // Send blank message on stderr to include a divider between prelude and app starting
return super.attach(processedAttachArgs, processedAttachArgs.url);
});
.then((projectType) => generator.add('projectType', projectType, false))
.then(() => {
this.outputLogger(`Attaching to ${platform}`);
switch (platform) {
case 'android':
generator.add('platform', platform, false);
return this.attachAndroid(attachArgs);
case 'ios':
generator.add('platform', platform, false);
return this.attachIos(attachArgs);
default:
generator.add('unknownPlatform', platform, true);
throw new Error(`Unknown Platform: ${platform}`);
}
}).then((processedAttachArgs: IAttachRequestArgs & { url?: string }) => {
this.outputLogger('Attaching to app.');
this.outputLogger('', true); // Send blank message on stderr to include a divider between prelude and app starting
return super.attach(processedAttachArgs, processedAttachArgs.url);
});
}).done(resolve, reject));
}
public disconnect(): Promise<void> {
return super.disconnect().then(() => {
const errorLogger = (message) => this.outputLogger(message, true);
// Stop ADB port forwarding if necessary
let adbPortPromise: Q.Promise<void>;
if (this.adbPortForwardingInfo) {
const adbForwardStopArgs =
['-s', this.adbPortForwardingInfo.targetDevice,
'forward',
'--remove', `tcp:${this.adbPortForwardingInfo.port}`];
const errorLogger = (message) => this.outputLogger(message, true);
return execCommand('adb', adbForwardStopArgs, errorLogger)
.then(() => {});
'forward',
'--remove', `tcp:${this.adbPortForwardingInfo.port}`];
adbPortPromise = execCommand('adb', adbForwardStopArgs, errorLogger)
.then(() => void 0);
} else {
adbPortPromise = Q<void>(void 0);
}
// Kill the Ionic dev server if necessary
let killServePromise: Q.Promise<void>;
if (this.ionicLivereloadProcess) {
killServePromise = killChildProcess(this.ionicLivereloadProcess, errorLogger).then(() => {
this.ionicLivereloadProcess = null;
});
} else {
killServePromise = Q<void>(void 0);
}
// Wait on all the cleanups
return Q.allSettled([adbPortPromise, killServePromise]).then(() => void 0);
});
}
private launchAndroid(launchArgs: ICordovaLaunchRequestArgs): Q.Promise<void> {
private launchAndroid(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType): Q.Promise<void> {
let workingDirectory = launchArgs.cwd;
let errorLogger = (message) => this.outputLogger(message, true);
// Launch the app
// Prepare the command line args
let isDevice = launchArgs.target.toLowerCase() === 'device';
let args = ['run', 'android', isDevice ? '--device' : '--emulator', '--verbose'];
let args = ['run', 'android', isDevice ? '--device' : '--emulator', '--verbose'];
if (['device', 'emulator'].indexOf(launchArgs.target.toLowerCase()) === -1) {
args.push(`--target=${launchArgs.target}`);
}
// If we are using Ionic livereload, we let Ionic do the launch
if (projectType.ionic && !launchArgs.noLivereload) {
args.push('--livereload');
return this.startIonicDevServer(launchArgs, args).then(() => void 0);
}
// Launch the app manually if not livereloading
let cordovaResult = cordovaRunCommand(args, errorLogger, workingDirectory).then((output) => {
let runOutput = output[0];
let stderr = output[1];
@ -130,56 +168,56 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
// Determine which device/emulator we are targeting
let adbDevicesResult = execCommand('adb', ['devices'], errorLogger)
.then((devicesOutput) => {
if (attachArgs.target.toLowerCase() === 'device') {
let deviceMatch = /\n([^\t]+)\tdevice($|\n)/m.exec(devicesOutput.replace(/\r/g, ''));
if (!deviceMatch) {
errorLogger(devicesOutput);
throw new Error('Unable to find device');
.then((devicesOutput) => {
if (attachArgs.target.toLowerCase() === 'device') {
let deviceMatch = /\n([^\t]+)\tdevice($|\n)/m.exec(devicesOutput.replace(/\r/g, ''));
if (!deviceMatch) {
errorLogger(devicesOutput);
throw new Error('Unable to find device');
}
return deviceMatch[1];
} else {
let emulatorMatch = /\n(emulator[^\t]+)\tdevice($|\n)/m.exec(devicesOutput.replace(/\r/g, ''));
if (!emulatorMatch) {
errorLogger(devicesOutput);
throw new Error('Unable to find emulator');
}
return emulatorMatch[1];
}
return deviceMatch[1];
} else {
let emulatorMatch = /\n(emulator[^\t]+)\tdevice($|\n)/m.exec(devicesOutput.replace(/\r/g, ''));
if (!emulatorMatch) {
errorLogger(devicesOutput);
throw new Error('Unable to find emulator');
}, (err: Error): any => {
let errorCode: string = (<any>err).code;
if (errorCode && errorCode === 'ENOENT') {
throw new Error('Unable to find adb. Please ensure it is in your PATH and re-open Visual Studio Code');
}
return emulatorMatch[1];
}
}, (err: Error): any => {
let errorCode: string = (<any>err).code;
if (errorCode && errorCode === 'ENOENT') {
throw new Error('Unable to find adb. Please ensure it is in your PATH and re-open Visual Studio Code');
}
throw err;
});
throw err;
});
let packagePromise = Q.nfcall(fs.readFile, path.join(attachArgs.cwd, 'platforms', 'android', 'AndroidManifest.xml'))
.then((manifestContents) => {
let parsedFile = elementtree.XML(manifestContents.toString());
let packageKey = 'package';
return parsedFile.attrib[packageKey];
});
.then((manifestContents) => {
let parsedFile = elementtree.XML(manifestContents.toString());
let packageKey = 'package';
return parsedFile.attrib[packageKey];
});
return Q.all([packagePromise, adbDevicesResult]).spread((appPackageName, targetDevice) => {
let getPidCommandArguments = ['-s', targetDevice, 'shell', `ps | grep ${appPackageName}`];
let findPidFunction = () => execCommand('adb', getPidCommandArguments, errorLogger).then((pidLine: string) => /^[^ ]+ +([^ ]+) /m.exec(pidLine));
return CordovaDebugAdapter.retryAsync(findPidFunction, (match) => !!match, 5, 1, 5000, 'Unable to find pid of cordova app').then((match: RegExpExecArray) => match[1])
.then((pid) => {
// Configure port forwarding to the app
let forwardSocketCommandArguments = ['-s', targetDevice, 'forward', `tcp:${attachArgs.port}`, `localabstract:webview_devtools_remote_${pid}`];
this.outputLogger('Forwarding debug port');
return execCommand('adb', forwardSocketCommandArguments, errorLogger).then(() => {
this.adbPortForwardingInfo = {targetDevice, port: attachArgs.port};
return CordovaDebugAdapter.retryAsync(findPidFunction, (match) => !!match, 5, 1, 5000, 'Unable to find pid of cordova app').then((match: RegExpExecArray) => match[1])
.then((pid) => {
// Configure port forwarding to the app
let forwardSocketCommandArguments = ['-s', targetDevice, 'forward', `tcp:${attachArgs.port}`, `localabstract:webview_devtools_remote_${pid}`];
this.outputLogger('Forwarding debug port');
return execCommand('adb', forwardSocketCommandArguments, errorLogger).then(() => {
this.adbPortForwardingInfo = { targetDevice, port: attachArgs.port };
});
});
});
}).then(() => {
let args: IAttachRequestArgs = {port: attachArgs.port, webRoot: attachArgs.cwd, cwd: attachArgs.cwd };
let args: IAttachRequestArgs = { port: attachArgs.port, webRoot: attachArgs.cwd, cwd: attachArgs.cwd };
return args;
});
}
private launchIos(launchArgs: ICordovaLaunchRequestArgs): Q.Promise<void> {
private launchIos(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType): Q.Promise<void> {
if (os.platform() !== 'darwin') {
return Q.reject<void>('Unable to launch iOS on non-mac machines');
}
@ -193,7 +231,7 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
if (launchArgs.target.toLowerCase() === 'device') {
// cordova run ios does not terminate, so we do not know when to try and attach.
// Instead, we try to launch manually using homebrew.
return cordovaRunCommand(['build', 'ios', '--device'], errorLogger, workingDirectory).then(() => {
return cordovaRunCommand(['build', 'ios', '--device'], errorLogger, workingDirectory).then(() => {
let buildFolder = path.join(workingDirectory, 'platforms', 'ios', 'build', 'device');
this.outputLogger('Installing app on device');
@ -221,7 +259,7 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
return CordovaIosDeviceLauncher.startDebugProxy(iosDebugProxyPort).then(() => {
return CordovaIosDeviceLauncher.startApp(appBundleId, iosDebugProxyPort, appStepLaunchTimeout);
});
}).then(() => void(0));
}).then(() => void (0));
} else {
let target = launchArgs.target.toLowerCase() === 'emulator' ? null : launchArgs.target;
if (target) {
@ -232,9 +270,9 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
errorLogger(output[0].replace(/\*+[^*]+\*+/g, '')); // Print out list of targets, without ** RUN SUCCEEDED **
throw err;
});
}).then(() => void(0));
}).then(() => void (0));
} else {
return cordovaRunCommand(['emulate', 'ios'], errorLogger, workingDirectory).then(() => void(0));
return cordovaRunCommand(['emulate', 'ios'], errorLogger, workingDirectory).then(() => void (0));
}
}
}
@ -253,12 +291,12 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
.then(path.basename);
} else {
return Q.nfcall(fs.readdir, path.join(attachArgs.cwd, 'platforms', 'ios', 'build', 'emulator')).then((entries: string[]) => {
let filtered = entries.filter((entry) => /\.app$/.test(entry));
if (filtered.length > 0) {
return filtered[0];
} else {
throw new Error('Unable to find .app file');
}
let filtered = entries.filter((entry) => /\.app$/.test(entry));
if (filtered.length > 0) {
return filtered[0];
} else {
throw new Error('Unable to find .app file');
}
});
}
}).then((packagePath) => {
@ -267,7 +305,7 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
let endpointsList = JSON.parse(response);
let devices = endpointsList.filter((entry) =>
attachArgs.target.toLowerCase() === 'device' ? entry.deviceId !== 'SIMULATOR'
: entry.deviceId === 'SIMULATOR'
: entry.deviceId === 'SIMULATOR'
);
let device = devices[0];
// device.url is of the form 'localhost:port'
@ -278,23 +316,176 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
}).then((targetPort) => {
let findWebviewFunc = () => {
return this.promiseGet(`http://localhost:${targetPort}/json`, 'Unable to communicate with target')
.then((response: string) => {
try {
let webviewsList = JSON.parse(response);
return webviewsList.filter((entry) => entry.url.indexOf(encodeURIComponent(packagePath)) !== -1);
} catch (e) {
throw new Error('Unable to find target app');
}
});
.then((response: string) => {
try {
let webviewsList = JSON.parse(response);
return webviewsList.filter((entry) => entry.url.indexOf(encodeURIComponent(packagePath)) !== -1);
} catch (e) {
throw new Error('Unable to find target app');
}
});
};
return CordovaDebugAdapter.retryAsync(findWebviewFunc, (webviewList) => webviewList.length > 0, attachArgs.attachAttempts, 1, attachArgs.attachDelay, 'Unable to find webview')
.then((relevantViews) => {
return {port: targetPort, url: relevantViews[0].url};
});
.then((relevantViews) => {
return { port: targetPort, url: relevantViews[0].url };
});
});
}).then(({port, url}) => {
return {port: port, webRoot: attachArgs.cwd, cwd: attachArgs.cwd, url: url};
return { port: port, webRoot: attachArgs.cwd, cwd: attachArgs.cwd, url: url };
});
}
private launchBrowser(launchArgs: ICordovaLaunchRequestArgs, projectType: IProjectType): Q.Promise<void> {
let workingDirectory = launchArgs.cwd;
let errorLogger = (message) => this.outputLogger(message, true);
// Currently, browser is only supported for Ionic projects
if (!projectType.ionic) {
let errorMessage = 'Browser is currently only supported for Ionic projects';
errorLogger(errorMessage);
return Q.reject<void>(new Error(errorMessage));
}
// Set up "ionic serve" args
let ionicServeArgs: string[] = [
"serve",
'--nobrowser'
];
if (launchArgs.noLivereload) {
ionicServeArgs.push('--nolivereload');
}
// Deploy app to browser
return Q(void 0).then(() => {
return this.startIonicDevServer(launchArgs, ionicServeArgs);
}).then((devServerUrl: string) => {
// Prepare Chrome launch args
launchArgs.url = devServerUrl;
launchArgs.userDataDir = path.join(settingsHome(), CordovaDebugAdapter.CHROME_DATA_DIR);
// Launch Chrome and attach
this.outputLogger('Attaching to app');
return super.launch(launchArgs);
}).catch((err) => {
errorLogger(err.message);
// Kill the Ionic dev server if necessary
let killPromise: Q.Promise<void>;
if (this.ionicLivereloadProcess) {
killPromise = killChildProcess(this.ionicLivereloadProcess, errorLogger);
} else {
killPromise = Q<void>(void 0);
}
// Propagate the error
return killPromise.finally(() => {
this.ionicLivereloadProcess = null;
Q.reject<void>(err)
});
});
}
/**
* Starts an Ionic livereload server ("serve or "run / emulate --livereload"). Returns a promise fulfulled with the full URL to the server.
*/
private startIonicDevServer(launchArgs: ICordovaLaunchRequestArgs, cliArgs: string[]): Q.Promise<string> {
if (launchArgs.devServerAddress) {
cliArgs.concat(['--address', launchArgs.devServerAddress]);
}
if (launchArgs.devServerPort) {
cliArgs.concat(['--port', launchArgs.devServerPort.toString()]);
}
let isServe: boolean = cliArgs[0] === 'serve';
let serverReady: boolean = false;
let serverReadyTimeout: number = 10000;
let appReadyTimeout: number = 120000; // If we're not serving, the app needs to build and deploy (and potentially start the emulator), which can be very long
let isLivereloading: boolean = isServe && cliArgs.indexOf('--nolivereload') === -1 || cliArgs.indexOf('--livereload') !== -1;
let serverDeferred = Q.defer<void>();
let appDeferred = Q.defer<void>();
let serverOutput: string = '';
this.ionicLivereloadProcess = cordovaStartCommand(cliArgs, launchArgs.cwd);
this.ionicLivereloadProcess.on('error', (err) => {
if (err.code === 'ENOENT') {
serverDeferred.reject(new Error('Ionic not found, please run \'npm install –g ionic\' to install it globally'));
} else {
serverDeferred.reject(err);
}
});
let serverOutputHandler = (data: Buffer) => {
serverOutput += data.toString();
// Listen for the server to be ready. We check for the "Ionic server commands" string to decide that.
//
// Exemple output of Ionic dev server:
//
// Running live reload server: undefined
// Watching: 0=www/**/*, 1=!www/lib/**/*
// Running dev server: http://localhost:8100
// Ionic server commands, enter:
// restart or r to restart the client app from the root
// goto or g and a url to have the app navigate to the given url
// consolelogs or c to enable/disable console log output
// serverlogs or s to enable/disable server log output
// quit or q to shutdown the server and exit
//
// ionic $
if (!serverReady && /Ionic server commands/.test(serverOutput)) {
serverReady = true;
serverDeferred.resolve(void 0);
}
if (serverReady) {
// Now that the server is ready, listen for the app to be ready as well. For "serve", this is always true, because no build and deploy is involved. For "run" and "emulate", we need to
// wait until we encounter the "Ionic server commands" string a second time. The reason for that is that the output of the server will be the same as above, plus the build output, and
// finally the "Ionic server commands" part will be printed again at the very end.
if (isServe || /Ionic server commands(?:.*\r?\n)*.*Ionic server commands/.test(serverOutput)) {
this.ionicLivereloadProcess.stdout.removeListener('data', serverOutputHandler);
appDeferred.resolve(void 0);
}
}
if (/Address Selection:/.test(serverOutput)) {
// Ionic does not know which address to use for the dev server, and requires human interaction; error out and let the user know
let errorMessage: string = 'Multiple addresses available for the Ionic dev server, please specify the \'devServerAddress\' property in the launch config.';
let addresses: string[] = /(\d+\) .*)/gm.exec(serverOutput);
if (addresses) {
// Give the user the list of addresses that Ionic found
addresses.shift();
errorMessage += [' Available addresses:'].concat(addresses).join(os.EOL + ' ');
}
serverDeferred.reject(new Error(errorMessage));
}
};
this.ionicLivereloadProcess.stdout.on('data', serverOutputHandler);
this.outputLogger(`Starting Ionic dev server (live reload: ${isLivereloading})`);
return serverDeferred.promise.timeout(serverReadyTimeout, `Starting the Ionic dev server timed out (${serverReadyTimeout} ms)`).then(() => {
this.outputLogger('Building and deploying app');
return appDeferred.promise.timeout(appReadyTimeout, `Building and deploying the app timed out (${appReadyTimeout} ms)`);
}).then(() => {
// Find the dev server full URL
let match: string[] = /Running dev server:.*(http:\/\/.*)/gm.exec(serverOutput);
if (!match || match.length < 2) {
return Q.reject<string>(new Error('Unable to determine the Ionic dev server address, please try re-launching the debugger'));
}
// The dev server address is the captured group at index 1 of the match
return Q(match[1]);
});
}
@ -314,7 +505,7 @@ export class CordovaDebugAdapter extends WebKitDebugAdapter {
private promiseGet(url: string, reqErrMessage: string): Q.Promise<string> {
let deferred = Q.defer<string>();
let req = http.get(url, function (res) {
let req = http.get(url, function(res) {
let responseString = '';
res.on('data', (data: Buffer) => {
responseString += data.toString();

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

@ -17,6 +17,7 @@ interface IPendingBreakpoint {
export class CordovaPathTransformer implements IDebugTransformer {
private _cordovaRoot: string;
private _platform: string;
private _webRoot: string;
private _clientPathToWebkitUrl = new Map<string, string>();
private _webkitUrlToClientPath = new Map<string, string>();
private _shadowedClientPaths = new Map<string, string>();
@ -34,6 +35,7 @@ export class CordovaPathTransformer implements IDebugTransformer {
public attach(args: ICordovaAttachRequestArgs): void {
this._cordovaRoot = args.cwd;
this._platform = args.platform.toLowerCase();
this._webRoot = args.webRoot || this._cordovaRoot;
}
public setBreakpoints(args: ISetBreakpointsArgs): Promise<void> {
@ -82,9 +84,9 @@ export class CordovaPathTransformer implements IDebugTransformer {
const clientPath = this.getClientPath(webkitUrl);
if (!clientPath) {
utils.Logger.log(`Paths.scriptParsed: could not resolve ${webkitUrl} to a file in the workspace. webRoot: ${this._cordovaRoot}`);
utils.Logger.log(`Paths.scriptParsed: could not resolve ${webkitUrl} to a file in the workspace. webRoot: ${this._webRoot}`);
} else {
utils.Logger.log(`Paths.scriptParsed: resolved ${webkitUrl} to ${clientPath}. webRoot: ${this._cordovaRoot}`);
utils.Logger.log(`Paths.scriptParsed: resolved ${webkitUrl} to ${clientPath}. webRoot: ${this._webRoot}`);
this._clientPathToWebkitUrl.set(clientPath, webkitUrl);
this._webkitUrlToClientPath.set(webkitUrl, clientPath);
@ -119,13 +121,27 @@ export class CordovaPathTransformer implements IDebugTransformer {
}
public getClientPath(sourceUrl: string): string {
let wwwRoot = path.join(this._cordovaRoot, 'www');
// Given an absolute file:/// (such as from the iOS simulator) vscode-chrome-debug's
// default behavior is to use that exact file, if it exists. We don't want that,
// since we know that those files are copies of files in the local folder structure.
// A simple workaround for this is to convert file:// paths to bogus http:// paths
sourceUrl = sourceUrl.replace('file:///', 'http://localhost/');
let defaultPath = utils.webkitUrlToClientPath(this._cordovaRoot, sourceUrl);
let wwwRoot = path.join(this._cordovaRoot, 'www');
// Find the mapped local file. Try looking first in the user-specified webRoot, then in the project root, and then in the www folder
let defaultPath = '';
[this._webRoot, this._cordovaRoot, wwwRoot].find((searchFolder) => {
let mappedPath = utils.webkitUrlToClientPath(searchFolder, sourceUrl);
if (mappedPath) {
defaultPath = mappedPath;
return true;
}
return false;
});
if (defaultPath.toLowerCase().indexOf(wwwRoot.toLowerCase()) === 0) {
// If the path appears to be in www, check to see if it exists in /merges/<platform>/<relative path>
let relativePath = path.relative(wwwRoot, defaultPath);
@ -144,4 +160,3 @@ export class CordovaPathTransformer implements IDebugTransformer {
return defaultPath;
}
}
;

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

@ -2,12 +2,14 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.
import * as child_process from 'child_process';
import {CordovaProjectHelper} from '../utils/cordovaProjectHelper';
import * as os from 'os';
import * as Q from 'q';
import * as util from 'util';
export function execCommand(command: string, args: string[], errorLogger: (message: string) => void): Q.Promise<string> {
let deferred = Q.defer<string>();
let proc = child_process.spawn(command, args, {stdio: 'pipe'});
let proc = child_process.spawn(command, args, { stdio: 'pipe' });
let stderr = '';
let stdout = '';
proc.stderr.on('data', (data: Buffer) => {
@ -33,11 +35,11 @@ export function execCommand(command: string, args: string[], errorLogger: (messa
export function cordovaRunCommand(args: string[], errorLogger: (message: string) => void, cordovaRootPath: string): Q.Promise<string[]> {
let defer = Q.defer<string[]>();
let cliName = CordovaProjectHelper.isIonicProject(cordovaRootPath) ? 'ionic' : 'cordova';
let output = '';
let stderr = '';
let cordovaCommand = `cordova${os.platform() === 'win32' ? '.cmd' : ''}`;
let process = child_process.spawn(cordovaCommand, args, {cwd: cordovaRootPath});
let process = cordovaStartCommand(args, cordovaRootPath);
process.stderr.on('data', data => {
stderr += data.toString();
});
@ -48,7 +50,7 @@ export function cordovaRunCommand(args: string[], errorLogger: (message: string)
if (exitCode) {
errorLogger(stderr);
errorLogger(output);
defer.reject(new Error(`'cordova ${args.join(' ')}' failed with exit code ${exitCode}.`));
defer.reject(new Error(util.format("'%s %s' failed with exit code %d", cliName, args.join(' '), exitCode)));
} else {
defer.resolve([output, stderr]);
}
@ -58,4 +60,30 @@ export function cordovaRunCommand(args: string[], errorLogger: (message: string)
});
return defer.promise;
}
export function cordovaStartCommand(args: string[], cordovaRootPath: string): child_process.ChildProcess {
let cliName = CordovaProjectHelper.isIonicProject(cordovaRootPath) ? 'ionic' : 'cordova';
let commandExtension = os.platform() === 'win32' ? '.cmd' : '';
let command = cliName + commandExtension;
return child_process.spawn(command, args, { cwd: cordovaRootPath });
}
export function killChildProcess(childProcess: child_process.ChildProcess, errorLogger: (message: string) => void): Q.Promise<void> {
if (process.platform === "win32") {
// Use taskkill to reliably kill the child process on all versions of Windows
let command: string = "taskkill";
let args: string[] = [
"/pid",
childProcess.pid.toString(),
"/T",
"/F"
];
return execCommand(command, args, errorLogger).then(() => void 0);
} else {
childProcess.kill();
return Q<void>(void 0);
}
}

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

@ -63,4 +63,4 @@ export class CordovaCommandHelper {
.then(() => deferred.promise);
});
}
}
}

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

@ -0,0 +1,22 @@
/**
*******************************************************
* *
* Copyright (C) Microsoft. All rights reserved. *
* *
*******************************************************
*/
import * as os from 'os';
import * as path from 'path';
export function settingsHome(): string {
switch (os.platform()) {
case 'win32':
return path.join(process.env['APPDATA'], 'vscode-cordova');
case 'darwin':
case 'linux':
return path.join(process.env['HOME'], '.vscode-cordova');
default:
throw new Error('UnexpectedPlatform');
};
}

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

@ -19,6 +19,8 @@ import * as Q from 'q';
import * as readline from 'readline';
import * as winreg from 'winreg';
import {settingsHome} from './settingsHelper'
// for poking around at internal applicationinsights options
var sender = require ('applicationinsights/Library/Sender');
var telemetryLogger = require ('applicationinsights/Library/Logging');
@ -162,20 +164,8 @@ export module Telemetry {
private static INTERNAL_DOMAIN_SUFFIX: string = 'microsoft.com';
private static INTERNAL_USER_ENV_VAR: string = 'TACOINTERNAL';
private static get settingsHome(): string {
switch (os.platform()) {
case 'win32':
return path.join(process.env['APPDATA'], 'vscode-cordova');
case 'darwin':
case 'linux':
return path.join(process.env['HOME'], '.vscode-cordova');
default:
throw new Error('UnexpectedPlatform');
};
}
private static get telemetrySettingsFile(): string {
return path.join(TelemetryUtils.settingsHome, TelemetryUtils.TELEMETRY_SETTINGS_FILENAME);
return path.join(settingsHome(), TelemetryUtils.TELEMETRY_SETTINGS_FILENAME);
}
public static init(appVersion: string, isOptedInValue: boolean): Q.Promise<any> {
@ -325,8 +315,8 @@ export module Telemetry {
* Save settings data in settingsHome/TelemetrySettings.json
*/
private static saveSettings(): void {
if (!fs.existsSync(TelemetryUtils.settingsHome)) {
fs.mkdirSync(TelemetryUtils.settingsHome);
if (!fs.existsSync(settingsHome())) {
fs.mkdirSync(settingsHome());
}
fs.writeFileSync(TelemetryUtils.telemetrySettingsFile, JSON.stringify(TelemetryUtils.telemetrySettings));