microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ea42d4004854292e5d6c536c4d57f8a3b348ee18

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/commandExecutor.ts

254lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as path from "path";
5import * as cp from "child_process";
6import { ILogger } from "../extension/log/LogHelper";
7import { NullLogger } from "../extension/log/NullLogger";
8import { ProjectVersionHelper } from "../common/projectVersionHelper";
9import { ISpawnResult } from "./node/childProcess";
10import { HostPlatform, HostPlatformId } from "./hostPlatform";
11import { ErrorHelper } from "./error/errorHelper";
12import { InternalErrorCode } from "./error/internalErrorCode";
13import * as nls from "vscode-nls";
14import { Node } from "./node/node";
15nls.config({
16 messageFormat: nls.MessageFormat.bundle,
17 bundleFormat: nls.BundleFormat.standalone,
18})();
19const localize = nls.loadMessageBundle();
20
21export enum CommandVerbosity {
22 OUTPUT,
23 SILENT,
24 PROGRESS,
25}
26
27interface EnvironmentOptions {
28 REACT_DEBUGGER?: string;
29}
30
31interface Options {
32 env?: EnvironmentOptions;
33 verbosity?: CommandVerbosity;
34 cwd?: string;
35}
36
37export enum CommandStatus {
38 Start = 0,
39 End = 1,
40}
41
42export class CommandExecutor {
43 public static ReactNativeCommand: string | null;
44 private childProcess = new Node.ChildProcess();
45
46 constructor(
47 private currentWorkingDirectory: string = process.cwd(),
48 private logger: ILogger = new NullLogger(),
49 ) {}
50
51 public execute(command: string, options: Options = {}): Promise<void> {
52 this.logger.debug(CommandExecutor.getCommandStatusString(command, CommandStatus.Start));
53 return this.childProcess
54 .execToString(command, { cwd: this.currentWorkingDirectory, env: options.env })
55 .then(
56 stdout => {
57 this.logger.info(stdout);
58 this.logger.debug(
59 CommandExecutor.getCommandStatusString(command, CommandStatus.End),
60 );
61 },
62 (reason: Error) => this.generateRejectionForCommand(command, reason),
63 );
64 }
65
66 /**
67 * Spawns a child process with the params passed
68 * This method waits until the spawned process finishes execution
69 * {command} - The command to be invoked in the child process
70 * {args} - Arguments to be passed to the command
71 * {options} - additional options with which the child process needs to be spawned
72 */
73 public spawn(command: string, args: string[], options: Options = {}): Promise<any> {
74 return this.spawnChildProcess(command, args, options).outcome;
75 }
76
77 /**
78 * Spawns the React Native packager in a child process.
79 */
80 public spawnReactPackager(args: string[], options: Options = {}): ISpawnResult {
81 return this.spawnReactCommand("start", args, options);
82 }
83
84 public getReactNativeVersion(): Promise<string> {
85 return ProjectVersionHelper.getReactNativeVersions(this.currentWorkingDirectory).then(
86 versions => versions.reactNativeVersion,
87 );
88 }
89
90 /**
91 * Kills the React Native packager in a child process.
92 */
93 public killReactPackager(packagerProcess?: cp.ChildProcess): Promise<void> {
94 if (packagerProcess) {
95 return new Promise(resolve => {
96 if (HostPlatform.getPlatformId() === HostPlatformId.WINDOWS) {
97 return resolve(
98 this.childProcess
99 .exec("taskkill /pid " + packagerProcess.pid + " /T /F")
100 .then(res => res.outcome),
101 );
102 } else {
103 packagerProcess.kill();
104 return resolve();
105 }
106 }).then(() => {
107 this.logger.info(localize("PackagerStopped", "Packager stopped"));
108 });
109 } else {
110 this.logger.warning(localize("PackagerNotFound", "Packager not found"));
111 return Promise.resolve();
112 }
113 }
114
115 /**
116 * Executes a react native command and waits for its completion.
117 */
118 public spawnReactCommand(
119 command: string,
120 args: string[] = [],
121 options: Options = {},
122 ): ISpawnResult {
123 const reactCommand = HostPlatform.getNpmCliCommand(this.selectReactNativeCLI());
124 return this.spawnChildProcess(reactCommand, [command, ...args], options);
125 }
126
127 /**
128 * Spawns a child process with the params passed
129 * This method has logic to do while the command is executing
130 * {command} - The command to be invoked in the child process
131 * {args} - Arguments to be passed to the command
132 * {options} - additional options with which the child process needs to be spawned
133 */
134 public spawnWithProgress(
135 command: string,
136 args: string[],
137 options: Options = { verbosity: CommandVerbosity.OUTPUT },
138 ): Promise<void> {
139 const spawnOptions = Object.assign({}, { cwd: this.currentWorkingDirectory }, options);
140 const commandWithArgs = command + " " + args.join(" ");
141 const timeBetweenDots = 1500;
142 let lastDotTime = 0;
143
144 const printDot = () => {
145 const now = Date.now();
146 if (now - lastDotTime > timeBetweenDots) {
147 lastDotTime = now;
148 this.logger.logStream(".", process.stdout);
149 }
150 };
151
152 if (options.verbosity === CommandVerbosity.OUTPUT) {
153 this.logger.debug(
154 CommandExecutor.getCommandStatusString(commandWithArgs, CommandStatus.Start),
155 );
156 }
157
158 const result = this.childProcess.spawn(command, args, spawnOptions);
159
160 result.stdout.on("data", (data: Buffer) => {
161 if (options.verbosity === CommandVerbosity.OUTPUT) {
162 this.logger.logStream(data, process.stdout);
163 } else if (options.verbosity === CommandVerbosity.PROGRESS) {
164 printDot();
165 }
166 });
167
168 result.stderr.on("data", (data: Buffer) => {
169 if (options.verbosity === CommandVerbosity.OUTPUT) {
170 this.logger.logStream(data, process.stderr);
171 } else if (options.verbosity === CommandVerbosity.PROGRESS) {
172 printDot();
173 }
174 });
175
176 return new Promise((resolve, reject) => {
177 result.outcome = result.outcome.then(
178 () => {
179 if (options.verbosity === CommandVerbosity.OUTPUT) {
180 this.logger.debug(
181 CommandExecutor.getCommandStatusString(
182 commandWithArgs,
183 CommandStatus.End,
184 ),
185 );
186 }
187 this.logger.logStream("\n", process.stdout);
188 resolve();
189 },
190 reason => {
191 reject(reason);
192 return this.generateRejectionForCommand(commandWithArgs, reason);
193 },
194 );
195 });
196 }
197
198 public selectReactNativeCLI(): string {
199 return (
200 CommandExecutor.ReactNativeCommand ||
201 path.resolve(this.currentWorkingDirectory, "node_modules", ".bin", "react-native")
202 );
203 }
204
205 private spawnChildProcess(
206 command: string,
207 args: string[],
208 options: Options = {},
209 ): ISpawnResult {
210 const spawnOptions = Object.assign({}, { cwd: this.currentWorkingDirectory }, options);
211 const commandWithArgs = command + " " + args.join(" ");
212
213 this.logger.debug(
214 CommandExecutor.getCommandStatusString(commandWithArgs, CommandStatus.Start),
215 );
216 const result = this.childProcess.spawn(command, args, spawnOptions);
217
218 result.stderr.on("data", (data: Buffer) => {
219 this.logger.logStream(data, process.stderr);
220 });
221
222 result.stdout.on("data", (data: Buffer) => {
223 this.logger.logStream(data, process.stdout);
224 });
225
226 result.outcome = result.outcome.then(
227 () =>
228 this.logger.debug(
229 CommandExecutor.getCommandStatusString(commandWithArgs, CommandStatus.End),
230 ),
231 reason => this.generateRejectionForCommand(commandWithArgs, reason),
232 );
233 return result;
234 }
235
236 private generateRejectionForCommand(command: string, reason: any): Promise<void> {
237 return Promise.reject<void>(
238 reason.errorCode === InternalErrorCode.CommandFailed
239 ? reason
240 : ErrorHelper.getNestedError(reason, InternalErrorCode.CommandFailed, command),
241 );
242 }
243
244 private static getCommandStatusString(command: string, status: CommandStatus) {
245 switch (status) {
246 case CommandStatus.Start:
247 return `Executing command: ${command}`;
248 case CommandStatus.End:
249 return `Finished executing: ${command}`;
250 default:
251 throw ErrorHelper.getInternalError(InternalErrorCode.UnsupportedCommandStatus);
252 }
253 }
254}
255