microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.14.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/commandPaletteHandler.ts

433lines · modeblame

bef522ffMeena Kunnathur Balakrishnan10 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
6e179583digeff10 years ago4import * as vscode from "vscode";
5import * as Q from "q";
0a68f8dbArtem Egorov8 years ago6import * as XDL from "./exponent/xdlInterface";
df4bce40digeff10 years ago7import {SettingsHelper} from "./settingsHelper";
0a68f8dbArtem Egorov8 years ago8import {OutputChannelLogger} from "./log/OutputChannelLogger";
4787ec09Artem Egorov8 years ago9import {Packager} from "../common/packager";
db6fd42aRuslan Bikkinin7 years ago10import {TargetType, GeneralMobilePlatform} from "./generalMobilePlatform";
0a68f8dbArtem Egorov8 years ago11import {AndroidPlatform} from "./android/androidPlatform";
12import {IOSPlatform} from "./ios/iOSPlatform";
e3706a1cRedMickey6 years ago13import {ProjectVersionHelper, RNPackageVersions} from "../common/projectVersionHelper";
14import {ReactNativeProjectHelper} from "../common/reactNativeProjectHelper";
aeaf46bcMeena Kunnathur Balakrishnan10 years ago15import {TargetPlatformHelper} from "../common/targetPlatformHelper";
d976d077Meena Kunnathur Balakrishnan10 years ago16import {TelemetryHelper} from "../common/telemetryHelper";
0a68f8dbArtem Egorov8 years ago17import {ExponentHelper} from "./exponent/exponentHelper";
4edcda70Artem Egorov8 years ago18import {ReactDirManager} from "./reactDirManager";
19import {ExtensionServer} from "./extensionServer";
d1fc7f8aArtem Egorov8 years ago20import {IAndroidRunOptions, IIOSRunOptions} from "./launchArgs";
d8b69cd3Yuri Skorokhodov7 years ago21import {ExponentPlatform} from "./exponent/exponentPlatform";
6277aa08Yuri Skorokhodov7 years ago22import {spawn, ChildProcess} from "child_process";
23import {HostPlatform} from "../common/hostPlatform";
af1474acRedMickey6 years ago24import {CommandExecutor} from "../common/commandExecutor";
d7d405aeYuri Skorokhodov7 years ago25import * as nls from "vscode-nls";
af1474acRedMickey6 years ago26import {ErrorHelper} from "../common/error/errorHelper";
27import {InternalErrorCode} from "../common/error/internalErrorCode";
d7d405aeYuri Skorokhodov7 years ago28const localize = nls.loadMessageBundle();
4b37483dmax-mironov8 years ago29
4edcda70Artem Egorov8 years ago30interface IReactNativeStuff {
31packager: Packager;
2e432a9eArtem Egorov8 years ago32exponentHelper: ExponentHelper;
4edcda70Artem Egorov8 years ago33reactDirManager: ReactDirManager;
34extensionServer: ExtensionServer;
35}
640e6e98max-mironov8 years ago36
4edcda70Artem Egorov8 years ago37interface IReactNativeProject extends IReactNativeStuff {
2e432a9eArtem Egorov8 years ago38workspaceFolder: vscode.WorkspaceFolder;
7fa90b3bRedMickey6 years ago39reactNativeVersions?: RNPackageVersions;
2e432a9eArtem Egorov8 years ago40}
41
e1c05e69dlebu10 years ago42export class CommandPaletteHandler {
a740b79aYuri Skorokhodov7 years ago43public static elementInspector: ChildProcess | null;
4edcda70Artem Egorov8 years ago44private static projectsCache: {[key: string]: IReactNativeProject} = {};
2e432a9eArtem Egorov8 years ago45private static logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
6a465861max-mironov8 years ago46
4edcda70Artem Egorov8 years ago47public static addFolder(workspaceFolder: vscode.WorkspaceFolder, stuff: IReactNativeStuff): void {
db6fd42aRuslan Bikkinin7 years ago48this.logger.debug(`Command palette: added folder ${workspaceFolder.uri.fsPath}`);
4edcda70Artem Egorov8 years ago49this.projectsCache[workspaceFolder.uri.fsPath] = {
50...stuff,
2e432a9eArtem Egorov8 years ago51workspaceFolder,
52};
53}
bef522ffMeena Kunnathur Balakrishnan10 years ago54
4edcda70Artem Egorov8 years ago55public static getFolder(workspaceFolder: vscode.WorkspaceFolder): IReactNativeProject {
56return this.projectsCache[workspaceFolder.uri.fsPath];
2e432a9eArtem Egorov8 years ago57}
58
4edcda70Artem Egorov8 years ago59public static delFolder(workspaceFolder: vscode.WorkspaceFolder): void {
60delete this.projectsCache[workspaceFolder.uri.fsPath];
bef522ffMeena Kunnathur Balakrishnan10 years ago61}
62
0904a0d7Meena Kunnathur Balakrishnan10 years ago63/**
64* Starts the React Native packager
65*/
2e432a9eArtem Egorov8 years ago66public static startPackager(): Q.Promise<void> {
67return this.selectProject()
68.then((project: IReactNativeProject) => {
e3706a1cRedMickey6 years ago69return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(project.packager.getProjectPath())
7fa90b3bRedMickey6 years ago70.then(versions => {
af1474acRedMickey6 years ago71return this.executeCommandInContext("startPackager", project.workspaceFolder, () => {
72return project.packager.isRunning()
73.then((running) => {
74return running ? project.packager.stop() : Q.resolve(void 0);
75});
76})
77.then(() => project.packager.start());
78});
2e432a9eArtem Egorov8 years ago79});
bef522ffMeena Kunnathur Balakrishnan10 years ago80}
81
0904a0d7Meena Kunnathur Balakrishnan10 years ago82/**
83* Kills the React Native packager invoked by the extension's packager
84*/
2e432a9eArtem Egorov8 years ago85public static stopPackager(): Q.Promise<void> {
86return this.selectProject()
87.then((project: IReactNativeProject) => {
4787ec09Artem Egorov8 years ago88return this.executeCommandInContext("stopPackager", project.workspaceFolder, () => project.packager.stop());
2e432a9eArtem Egorov8 years ago89});
3194e9afMeena Kunnathur Balakrishnan10 years ago90}
91
4edcda70Artem Egorov8 years ago92public static stopAllPackagers(): Q.Promise<void> {
93let keys = Object.keys(this.projectsCache);
94let promises: Q.Promise<void>[] = [];
95keys.forEach((key) => {
96let project = this.projectsCache[key];
4787ec09Artem Egorov8 years ago97promises.push(this.executeCommandInContext("stopPackager", project.workspaceFolder, () => project.packager.stop()));
4edcda70Artem Egorov8 years ago98});
99
100return Q.all(promises).then(() => {});
101}
102
f2a58eefBret Johnson9 years ago103/**
104* Restarts the React Native packager
105*/
2e432a9eArtem Egorov8 years ago106public static restartPackager(): Q.Promise<void> {
107return this.selectProject()
108.then((project: IReactNativeProject) => {
e3706a1cRedMickey6 years ago109return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(project.packager.getProjectPath())
7fa90b3bRedMickey6 years ago110.then(versions => {
af1474acRedMickey6 years ago111return this.executeCommandInContext("restartPackager", project.workspaceFolder, () =>
112this.runRestartPackagerCommandAndUpdateStatus(project));
113});
2e432a9eArtem Egorov8 years ago114});
f2a58eefBret Johnson9 years ago115}
116
7893fb7eJimmy Thomson9 years ago117/**
118* Execute command to publish to exponent host.
119*/
2e432a9eArtem Egorov8 years ago120public static publishToExpHost(): Q.Promise<void> {
121return this.selectProject()
122.then((project: IReactNativeProject) => {
123return this.executeCommandInContext("publishToExpHost", project.workspaceFolder, () => {
124return this.executePublishToExpHost(project).then((didPublish) => {
125if (!didPublish) {
d7d405aeYuri Skorokhodov7 years ago126CommandPaletteHandler.logger.warning(localize("ExponentPublishingWasUnsuccessfulMakeSureYoureLoggedInToExpo", "Publishing was unsuccessful. Please make sure you are logged in Expo and your project is a valid Expo project"));
2e432a9eArtem Egorov8 years ago127}
128});
129});
7893fb7eJimmy Thomson9 years ago130});
131}
132
3194e9afMeena Kunnathur Balakrishnan10 years ago133/**
134* Executes the 'react-native run-android' command
135*/
d1fc7f8aArtem Egorov8 years ago136public static runAndroid(target: TargetType = "simulator"): Q.Promise<void> {
2e432a9eArtem Egorov8 years ago137return this.selectProject()
138.then((project: IReactNativeProject) => {
0611729dRuslan Bikkinin7 years ago139TargetPlatformHelper.checkTargetPlatformSupport("android");
e3706a1cRedMickey6 years ago140return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(project.packager.getProjectPath())
7fa90b3bRedMickey6 years ago141.then(versions => {
142project.reactNativeVersions = versions;
af1474acRedMickey6 years ago143return this.executeCommandInContext("runAndroid", project.workspaceFolder, () => {
144const platform = <AndroidPlatform>this.createPlatform(project, "android", AndroidPlatform, target);
145return platform.beforeStartPackager()
146.then(() => {
147return platform.startPackager();
148})
149.then(() => {
150return platform.runApp(/*shouldLaunchInAllDevices*/true);
151})
152.then(() => {
153return platform.disableJSDebuggingMode();
154});
2e432a9eArtem Egorov8 years ago155});
af1474acRedMickey6 years ago156});
0a68f8dbArtem Egorov8 years ago157});
3194e9afMeena Kunnathur Balakrishnan10 years ago158}
159
160/**
161* Executes the 'react-native run-ios' command
162*/
d1fc7f8aArtem Egorov8 years ago163public static runIos(target: TargetType = "simulator"): Q.Promise<void> {
2e432a9eArtem Egorov8 years ago164return this.selectProject()
165.then((project: IReactNativeProject) => {
e3706a1cRedMickey6 years ago166return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(project.packager.getProjectPath())
7fa90b3bRedMickey6 years ago167.then(versions => {
168project.reactNativeVersions = versions;
ba953e9fRedMickey6 years ago169TargetPlatformHelper.checkTargetPlatformSupport("ios");
170return this.executeCommandInContext("runIos", project.workspaceFolder, () => {
171const platform = <IOSPlatform>this.createPlatform(project, "ios", IOSPlatform, target);
172return platform.beforeStartPackager()
173.then(() => {
174return platform.startPackager();
175})
176.then(() => {
177// Set the Debugging setting to disabled, because in iOS it's persisted across runs of the app
178return platform.disableJSDebuggingMode();
179})
180.catch(() => { }) // If setting the debugging mode fails, we ignore the error and we run the run ios command anyways
181.then(() => {
182return platform.runApp();
183});
184});
af1474acRedMickey6 years ago185});
4787ec09Artem Egorov8 years ago186});
187}
188
189/**
190* Starts the Exponent packager
191*/
192public static runExponent(): Q.Promise<void> {
193return this.selectProject()
194.then((project: IReactNativeProject) => {
e3706a1cRedMickey6 years ago195return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(project.packager.getProjectPath())
7fa90b3bRedMickey6 years ago196.then(versions => {
af1474acRedMickey6 years ago197return this.loginToExponent(project)
198.then(() => {
199return this.executeCommandInContext("runExponent", project.workspaceFolder, () => {
7fa90b3bRedMickey6 years ago200project.reactNativeVersions = versions;
af1474acRedMickey6 years ago201const platform = <ExponentPlatform>this.createPlatform(project, "exponent", ExponentPlatform);
202return platform.beforeStartPackager()
203.then(() => {
204return platform.startPackager();
205})
206.then(() => {
207return platform.runApp();
208});
4787ec09Artem Egorov8 years ago209});
af1474acRedMickey6 years ago210});
4787ec09Artem Egorov8 years ago211});
2e432a9eArtem Egorov8 years ago212});
3194e9afMeena Kunnathur Balakrishnan10 years ago213}
214
2e432a9eArtem Egorov8 years ago215public static showDevMenu(): Q.Promise<void> {
a41f5c68Artem Egorov8 years ago216return this.selectProject()
217.then((project: IReactNativeProject) => {
db6fd42aRuslan Bikkinin7 years ago218const androidPlatform = <AndroidPlatform>this.createPlatform(project, "android", AndroidPlatform);
219androidPlatform.showDevMenu()
a41f5c68Artem Egorov8 years ago220.catch(() => { }); // Ignore any errors
2ecfbd20Yuri Skorokhodov7 years ago221
222if (process.platform === "darwin") {
223const iosPlatform = <IOSPlatform>this.createPlatform(project, "ios", IOSPlatform);
224iosPlatform.showDevMenu()
225.catch(() => { }); // Ignore any errors
226}
a41f5c68Artem Egorov8 years ago227return Q.resolve(void 0);
228});
7daed3fcArtem Egorov8 years ago229}
230
2e432a9eArtem Egorov8 years ago231public static reloadApp(): Q.Promise<void> {
a41f5c68Artem Egorov8 years ago232return this.selectProject()
233.then((project: IReactNativeProject) => {
db6fd42aRuslan Bikkinin7 years ago234const androidPlatform = <AndroidPlatform>this.createPlatform(project, "android", AndroidPlatform);
235androidPlatform.reloadApp()
a41f5c68Artem Egorov8 years ago236.catch(() => { }); // Ignore any errors
2ecfbd20Yuri Skorokhodov7 years ago237
238if (process.platform === "darwin") {
239const iosPlatform = <IOSPlatform>this.createPlatform(project, "ios", IOSPlatform);
240iosPlatform.reloadApp()
241.catch(() => { }); // Ignore any errors
242}
a41f5c68Artem Egorov8 years ago243return Q.resolve(void 0);
244});
7daed3fcArtem Egorov8 years ago245}
246
14fc3172Artem Egorov7 years ago247public static runElementInspector(): Q.Promise<void> {
248if (!CommandPaletteHandler.elementInspector) {
6277aa08Yuri Skorokhodov7 years ago249// Remove the following env variables to prevent running electron app in node mode.
250// https://github.com/Microsoft/vscode/issues/3011#issuecomment-184577502
251let env = Object.assign({}, process.env);
252delete env.ATOM_SHELL_INTERNAL_RUN_AS_NODE;
253delete env.ELECTRON_RUN_AS_NODE;
254let command = HostPlatform.getNpmCliCommand("react-devtools");
255CommandPaletteHandler.elementInspector = spawn(command, [], {
256env,
257});
258if (!CommandPaletteHandler.elementInspector.pid) {
259CommandPaletteHandler.elementInspector = null;
d7d405aeYuri Skorokhodov7 years ago260return Q.reject(ErrorHelper.getInternalError(InternalErrorCode.ReactDevtoolsIsNotInstalled));
fd2c4686Yuri Skorokhodov7 years ago261}
6277aa08Yuri Skorokhodov7 years ago262CommandPaletteHandler.elementInspector.stdout.on("data", (data: string) => {
263this.logger.info(data);
264});
265CommandPaletteHandler.elementInspector.stderr.on("data", (data: string) => {
266this.logger.error(data);
267});
268CommandPaletteHandler.elementInspector.once("exit", () => {
269CommandPaletteHandler.elementInspector = null;
270});
62e2a78aYuri Skorokhodov7 years ago271} else {
d7d405aeYuri Skorokhodov7 years ago272this.logger.info(localize("AnotherElementInspectorAlreadyRun", "Another element inspector already run"));
62e2a78aYuri Skorokhodov7 years ago273}
14fc3172Artem Egorov7 years ago274return Q(void 0);
d8b69cd3Yuri Skorokhodov7 years ago275}
14fc3172Artem Egorov7 years ago276
277public static stopElementInspector(): void {
278return CommandPaletteHandler.elementInspector ? CommandPaletteHandler.elementInspector.kill() : void 0;
279}
280
031832ffArtem Egorov8 years ago281public static getPlatformByCommandName(commandName: string): string {
282commandName = commandName.toLocaleLowerCase();
283
284if (commandName.indexOf("android") > -1) {
285return "android";
286}
287
288if (commandName.indexOf("ios") > -1) {
289return "ios";
290}
291
292if (commandName.indexOf("exponent") > -1) {
293return "exponent";
294}
295
296return "";
297}
298
db6fd42aRuslan Bikkinin7 years ago299private static createPlatform(project: IReactNativeProject, platform: "ios" | "android" | "exponent", platformClass: typeof GeneralMobilePlatform, target?: TargetType): GeneralMobilePlatform {
300const runOptions = CommandPaletteHandler.getRunOptions(project, platform, target);
301return new platformClass(runOptions, {
302packager: project.packager,
303});
304}
305
2e432a9eArtem Egorov8 years ago306private static runRestartPackagerCommandAndUpdateStatus(project: IReactNativeProject): Q.Promise<void> {
4787ec09Artem Egorov8 years ago307return project.packager.restart(SettingsHelper.getPackagerPort(project.workspaceFolder.uri.fsPath));
3194e9afMeena Kunnathur Balakrishnan10 years ago308}
b3a793eeNisheet Jain10 years ago309
310/**
311* Ensures that we are in a React Native project and then executes the operation
312* Otherwise, displays an error message banner
313* {operation} - a function that performs the expected operation
314*/
2e432a9eArtem Egorov8 years ago315private static executeCommandInContext(rnCommand: string, workspaceFolder: vscode.WorkspaceFolder, operation: () => Q.Promise<void>): Q.Promise<void> {
031832ffArtem Egorov8 years ago316const extProps = {
317platform: {
318value: CommandPaletteHandler.getPlatformByCommandName(rnCommand),
319isPii: false,
320},
321};
322
323return TelemetryHelper.generate("RNCommand", extProps, (generator) => {
8512ccfeMeena Kunnathur Balakrishnan10 years ago324generator.add("command", rnCommand, false);
e4dd9aa4Serge Svekolnikov8 years ago325const projectRoot = SettingsHelper.getReactNativeProjectRoot(workspaceFolder.uri.fsPath);
db6fd42aRuslan Bikkinin7 years ago326this.logger.debug(`Command palette: run project ${projectRoot} in context`);
327return ReactNativeProjectHelper.isReactNativeProject(projectRoot)
328.then(isRNProject => {
329generator.add("isRNProject", isRNProject, false);
330if (isRNProject) {
331// Bring the log channel to focus
332this.logger.setFocusOnLogChannel();
333
334// Execute the operation
335return operation();
336} else {
337vscode.window.showErrorMessage(`${projectRoot} workspace is not a React Native project.`);
338return;
339}
340});
10873e11digeff10 years ago341});
b3a793eeNisheet Jain10 years ago342}
7893fb7eJimmy Thomson9 years ago343
344/**
345* Publish project to exponent server. In order to do this we need to make sure the user is logged in exponent and the packager is running.
346*/
2e432a9eArtem Egorov8 years ago347private static executePublishToExpHost(project: IReactNativeProject): Q.Promise<boolean> {
d7d405aeYuri Skorokhodov7 years ago348CommandPaletteHandler.logger.info(localize("PublishingAppToExponentServer", "Publishing app to Expo server. This might take a moment."));
2e432a9eArtem Egorov8 years ago349return this.loginToExponent(project)
7059d307Patricio Beltran9 years ago350.then(user => {
2e432a9eArtem Egorov8 years ago351CommandPaletteHandler.logger.debug(`Publishing as ${user.username}...`);
4787ec09Artem Egorov8 years ago352return this.runExponent()
7059d307Patricio Beltran9 years ago353.then(() =>
a41f5c68Artem Egorov8 years ago354XDL.publish(project.workspaceFolder.uri.fsPath))
7059d307Patricio Beltran9 years ago355.then(response => {
356if (response.err || !response.url) {
357return false;
358}
d7d405aeYuri Skorokhodov7 years ago359const publishedOutput = localize("ExpoAppSuccessfullyPublishedTo", "Expo app successfully published to {0}", response.url);
2e432a9eArtem Egorov8 years ago360CommandPaletteHandler.logger.info(publishedOutput);
7059d307Patricio Beltran9 years ago361vscode.window.showInformationMessage(publishedOutput);
362return true;
363});
364});
365}
366
2e432a9eArtem Egorov8 years ago367private static loginToExponent(project: IReactNativeProject): Q.Promise<XDL.IUser> {
368return project.exponentHelper.loginToExponent(
5c8365a6Artem Egorov8 years ago369(message, password) => {
370return Q.Promise((resolve, reject) => {
371vscode.window.showInputBox({ placeHolder: message, password: password })
2e432a9eArtem Egorov8 years ago372.then(login => {
373resolve(login || "");
374}, reject);
5c8365a6Artem Egorov8 years ago375});
376},
377(message) => {
378return Q.Promise((resolve, reject) => {
379vscode.window.showInformationMessage(message)
2e432a9eArtem Egorov8 years ago380.then(password => {
381resolve(password || "");
382}, reject);
5c8365a6Artem Egorov8 years ago383});
384}
4787ec09Artem Egorov8 years ago385)
386.catch((err) => {
d7d405aeYuri Skorokhodov7 years ago387CommandPaletteHandler.logger.warning(localize("ExpoErrorOccuredMakeSureYouAreLoggedIn", "An error has occured. Please make sure you are logged in to Expo, your project is setup correctly for publishing and your packager is running as Expo."));
4787ec09Artem Egorov8 years ago388throw err;
389});
7893fb7eJimmy Thomson9 years ago390}
2e432a9eArtem Egorov8 years ago391
392private static selectProject(): Q.Promise<IReactNativeProject> {
393let keys = Object.keys(this.projectsCache);
394if (keys.length > 1) {
395return Q.Promise((resolve, reject) => {
396vscode.window.showQuickPick(keys)
397.then((selected) => {
398if (selected) {
db6fd42aRuslan Bikkinin7 years ago399this.logger.debug(`Command palette: selected project ${selected}`);
2e432a9eArtem Egorov8 years ago400resolve(this.projectsCache[selected]);
401}
402}, reject);
403});
404} else if (keys.length === 1) {
db6fd42aRuslan Bikkinin7 years ago405this.logger.debug(`Command palette: once project ${keys[0]}`);
2e432a9eArtem Egorov8 years ago406return Q.resolve(this.projectsCache[keys[0]]);
407} else {
d7d405aeYuri Skorokhodov7 years ago408return Q.reject(ErrorHelper.getInternalError(InternalErrorCode.WorkspaceNotFound, "Current workspace does not contain React Native projects."));
2e432a9eArtem Egorov8 years ago409}
410}
d1fc7f8aArtem Egorov8 years ago411
4787ec09Artem Egorov8 years ago412private static getRunOptions(project: IReactNativeProject, platform: "ios" | "android" | "exponent", target: TargetType = "simulator"): IAndroidRunOptions | IIOSRunOptions {
d1fc7f8aArtem Egorov8 years ago413const packagerPort = SettingsHelper.getPackagerPort(project.workspaceFolder.uri.fsPath);
414const runArgs = SettingsHelper.getRunArgs(platform, target, project.workspaceFolder.uri);
415const envArgs = SettingsHelper.getEnvArgs(platform, target, project.workspaceFolder.uri);
416const envFile = SettingsHelper.getEnvFile(platform, target, project.workspaceFolder.uri);
417const projectRoot = SettingsHelper.getReactNativeProjectRoot(project.workspaceFolder.uri.fsPath);
418const runOptions: IAndroidRunOptions | IIOSRunOptions = {
419platform: platform,
420workspaceRoot: project.workspaceFolder.uri.fsPath,
421projectRoot: projectRoot,
422packagerPort: packagerPort,
423runArguments: runArgs,
424env: envArgs,
425envFile: envFile,
7fa90b3bRedMickey6 years ago426reactNativeVersions: project.reactNativeVersions || {reactNativeVersion: "", reactNativeWindowsVersion: ""},
d1fc7f8aArtem Egorov8 years ago427};
428
af1474acRedMickey6 years ago429CommandExecutor.ReactNativeCommand = SettingsHelper.getReactNativeGlobalCommandName(project.workspaceFolder.uri);
430
d1fc7f8aArtem Egorov8 years ago431return runOptions;
432}
d7d405aeYuri Skorokhodov7 years ago433}