microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.6.5

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appcenter/command/commandExecutor.ts

497lines · 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 vscode from "vscode";
5import * as Q from "q";
6import * as qs from "qs";
7import * as os from "os";
8
9import { ILogger, LogLevel } from "../../log/LogHelper";
10import Auth from "../../appcenter/auth/auth";
11import { AppCenterLoginType, ACConstants, AppCenterOS, CurrentAppDeployments, Deployment, ACCommandNames } from "../appCenterConstants";
12import { Profile } from "../../appcenter/auth/profile/profile";
13import { SettingsHelper } from "../../settingsHelper";
14import { AppCenterClient, models } from "../api/index";
15import { DefaultApp, ICodePushReleaseParams } from "./commandParams";
16import { AppCenterExtensionManager } from "../appCenterExtensionManager";
17import { ACStrings } from "../appCenterStrings";
18import CodePushRelease from "../codepush/release";
19import { ACUtils } from "../helpers/utils";
20import { updateContents, reactNative, fileUtils } from "../codepush/codepush-sdk/src/index";
21import BundleConfig = reactNative.BundleConfig;
22import { getQPromisifiedClientResult } from "../api/createClient";
23import { validRange } from "semver";
24import { VsCodeUtils, IButtonMessageItem } from "../helpers/vscodeUtils";
25
26interface IAppCenterAuth {
27 login(appcenterManager: AppCenterExtensionManager): Q.Promise<void>;
28 logout(appcenterManager: AppCenterExtensionManager): Q.Promise<void>;
29 whoAmI(appCenterManager: AppCenterExtensionManager): Q.Promise<void>;
30}
31
32interface IAppCenterApps {
33 getCurrentApp(appCenterManager: AppCenterExtensionManager): Q.Promise<void>;
34 setCurrentApp(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void>;
35 setCurrentDeployment(appCenterManager: AppCenterExtensionManager): Q.Promise<void>;
36}
37
38interface IAppCenterCodePush {
39 showMenu(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void>;
40 releaseReact(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void>;
41 switchIsMandatoryForRelease(appCenterManager: AppCenterExtensionManager): Q.Promise<void>;
42 setTargetBinaryVersionForRelease(appCenterManager: AppCenterExtensionManager): Q.Promise<void>;
43}
44
45export class AppCenterCommandExecutor implements IAppCenterAuth, IAppCenterCodePush, IAppCenterApps {
46 private logger: ILogger;
47
48 constructor(logger: ILogger) {
49 this.logger = logger;
50 }
51
52 public login(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
53 const appCenterLoginOptions: string[] = Object.keys(AppCenterLoginType).filter(k => typeof AppCenterLoginType[k as any] === "number");
54 vscode.window.showQuickPick(appCenterLoginOptions, { placeHolder: ACStrings.SelectLoginTypeMsg })
55 .then((loginType) => {
56 switch (loginType) {
57 case (AppCenterLoginType[AppCenterLoginType.Interactive]):
58 const messageItems: IButtonMessageItem[] = [];
59 const loginUrl = `${SettingsHelper.getAppCenterLoginEndpoint()}?${qs.stringify({ hostname: os.hostname()})}`;
60 messageItems.push({ title : ACStrings.OkBtnLabel,
61 url : loginUrl });
62
63 return VsCodeUtils.ShowInformationMessage(ACStrings.PleaseLoginViaBrowser, ...messageItems)
64 .then((selection: IButtonMessageItem | undefined) => {
65 if (selection) {
66 return vscode.window.showInputBox({ prompt: ACStrings.PleaseProvideToken, ignoreFocusOut: true })
67 .then(token => {
68 this.loginWithToken(token, appCenterManager);
69 });
70 } else return Q.resolve(void 0);
71 });
72 case (AppCenterLoginType[AppCenterLoginType.Token]):
73 return vscode.window.showInputBox({ prompt: ACStrings.PleaseProvideToken , ignoreFocusOut: true})
74 .then(token => {
75 return this.loginWithToken(token, appCenterManager);
76 });
77 default:
78 // User canel login otherwise
79 return Q.resolve(void 0);
80 }
81 });
82 return Q.resolve(void 0);
83 }
84
85 public logout(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
86 return Auth.doLogout(appCenterManager.projectRootPath).then(() => {
87 VsCodeUtils.ShowInformationMessage(ACStrings.UserLoggedOutMsg);
88 return appCenterManager.setupAppCenterStatusBar(null);
89 }).catch(() => {
90 this.logger.log("An errro occured on logout", LogLevel.Error);
91 });
92 }
93
94 public whoAmI(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
95 return Auth.getProfile(appCenterManager.projectRootPath).then((profile: Profile | null) => {
96 if (profile && profile.displayName) {
97 VsCodeUtils.ShowInformationMessage(ACStrings.YouAreLoggedInMsg(profile.displayName));
98 } else {
99 VsCodeUtils.ShowInformationMessage(ACStrings.UserIsNotLoggedInMsg);
100 }
101 return Q.resolve(void 0);
102 });
103 }
104
105 public setCurrentDeployment(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
106 this.restoreCurrentApp(appCenterManager.projectRootPath)
107 .then((currentApp: DefaultApp) => {
108 if (currentApp && currentApp.currentAppDeployments && currentApp.currentAppDeployments.codePushDeployments) {
109 const deploymentOptions: string[] = currentApp.currentAppDeployments.codePushDeployments.map((deployment) => {
110 return deployment.name;
111 });
112 vscode.window.showQuickPick(deploymentOptions, { placeHolder: ACStrings.SelectCurrentDeploymentMsg })
113 .then((deploymentName) => {
114 if (deploymentName) {
115 this.saveCurrentApp(
116 appCenterManager.projectRootPath,
117 currentApp.identifier,
118 AppCenterOS[currentApp.os], {
119 currentDeploymentName: deploymentName,
120 codePushDeployments: currentApp.currentAppDeployments.codePushDeployments,
121 },
122 currentApp.targetBinaryVersion,
123 currentApp.isMandatory
124 );
125 VsCodeUtils.ShowInformationMessage(ACStrings.YourCurrentDeploymentMsg(deploymentName));
126 }
127 });
128 } else {
129 VsCodeUtils.ShowInformationMessage(ACStrings.NoCurrentAppSetMsg);
130 }
131 });
132 return Q.resolve(void 0);
133 }
134
135 public getCurrentApp(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
136 this.restoreCurrentApp(appCenterManager.projectRootPath).then((app: DefaultApp) => {
137 if (app) {
138 VsCodeUtils.ShowInformationMessage(ACStrings.YourCurrentAppMsg(app.identifier));
139 } else {
140 VsCodeUtils.ShowInformationMessage(ACStrings.NoCurrentAppSetMsg);
141 }
142 });
143 return Q.resolve(void 0);
144 }
145
146 public setCurrentApp(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
147 vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Get Apps"}, p => {
148 return new Promise((resolve, reject) => {
149 p.report({message: ACStrings.FetchAppsStatusBarMessage });
150 getQPromisifiedClientResult(client.account.apps.list()).then((apps: models.AppResponse[]) => {
151 const appsList: models.AppResponse[] = apps;
152 const reactNativeApps = appsList.filter(app => app.platform === ACConstants.AppCenterReactNativePlatformName);
153 resolve(reactNativeApps);
154 });
155 });
156 }).then((rnApps: models.AppResponse[]) => {
157 let options = rnApps.map(app => {
158 return {
159 label: `${app.name} (${app.os})`,
160 description: app.displayName,
161 target: app.name,
162 };
163 });
164 vscode.window.showQuickPick(options, { placeHolder: ACStrings.ProvideCurrentAppPromptMsg })
165 .then((selected: {label: string, description: string, target: string}) => {
166 if (selected) {
167 const selectedApps: models.AppResponse[] = rnApps.filter(app => app.name === selected.target);
168 if (selectedApps && selectedApps.length === 1) {
169 const selectedApp: models.AppResponse = selectedApps[0];
170 const selectedAppName: string = `${selectedApp.owner.name}/${selectedApp.name}`;
171 const OS: AppCenterOS = AppCenterOS[selectedApp.os.toLowerCase()];
172
173 vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Get Deployments"}, p => {
174 return new Promise((resolve, reject) => {
175 p.report({message: ACStrings.FetchDeploymentsStatusBarMessage });
176 getQPromisifiedClientResult(client.codepush.codePushDeployments.list(selectedApp.name, selectedApp.owner.name))
177 .then((deployments: models.Deployment[]) => {
178 resolve(deployments.sort((a, b): any => {
179 return a.name < b.name; // sort alphabetically
180 }));
181 });
182 });
183 })
184 .then((appDeployments: models.Deployment[]) => {
185 let currentDeployments: CurrentAppDeployments | null = null;
186 if (appDeployments.length > 0) {
187 const deployments: Deployment[] = appDeployments.map((d) => {
188 return {
189 name: d.name,
190 };
191 });
192 currentDeployments = {
193 codePushDeployments: deployments,
194 currentDeploymentName: appDeployments[0].name, // Select 1st one by default
195 };
196 }
197 this.saveCurrentApp(
198 appCenterManager.projectRootPath,
199 selectedAppName,
200 OS,
201 currentDeployments,
202 ACConstants.AppCenterDefaultTargetBinaryVersion,
203 ACConstants.AppCenterDefaultIsMandatoryParam)
204 .then((app: DefaultApp | null) => {
205 if (app) {
206 return VsCodeUtils.ShowInformationMessage(ACStrings.YourCurrentAppAndDeployemntMsg(selected.target
207 , app.currentAppDeployments.currentDeploymentName));
208 } else {
209 this.logger.error("Failed to save current app");
210 return Q.resolve(void 0);
211 }
212 });
213 });
214 }
215 }
216 });
217 });
218 return Q.resolve(void 0);
219 }
220
221 public releaseReact(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
222 let codePushRelaseParams = <ICodePushReleaseParams>{};
223 const projectRootPath: string = appCenterManager.projectRootPath;
224 return Q.Promise<any>((resolve, reject) => {
225 let updateContentsDirectory: string;
226 let isMandatory: boolean;
227 vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Get Apps" }, p => {
228 return new Promise<DefaultApp>((appResolve, appReject) => {
229 p.report({ message: ACStrings.GettingAppInfoMessage });
230 this.restoreCurrentApp(appCenterManager.projectRootPath)
231 .then((currentApp: DefaultApp) => appResolve(<DefaultApp>currentApp))
232 .catch(err => appReject(err));
233 }).then((currentApp: DefaultApp): any => {
234 p.report({ message: ACStrings.DetectingAppVersionMessage });
235 if (!currentApp) {
236 throw new Error(`No current app has been specified.`);
237 }
238 if (!currentApp.os || !reactNative.isValidOS(currentApp.os)) {
239 throw new Error(`OS must be "android", "ios", or "windows".`);
240 }
241 codePushRelaseParams.app = currentApp;
242 codePushRelaseParams.deploymentName = currentApp.currentAppDeployments.currentDeploymentName;
243 currentApp.os = currentApp.os.toLowerCase();
244 isMandatory = !!currentApp.isMandatory;
245 if (currentApp.targetBinaryVersion !== ACConstants.AppCenterDefaultTargetBinaryVersion) {
246 return currentApp.targetBinaryVersion;
247 } else {
248 switch (currentApp.os) {
249 case "android": return reactNative.getAndroidAppVersion(projectRootPath);
250 case "ios": return reactNative.getiOSAppVersion(projectRootPath);
251 case "windows": return reactNative.getWindowsAppVersion(projectRootPath);
252 default: throw new Error(`OS must be "android", "ios", or "windows".`);
253 }
254 }
255 }).then((appVersion: string) => {
256 p.report({ message: ACStrings.RunningBundleCommandMessage });
257 codePushRelaseParams.appVersion = appVersion;
258 return reactNative.makeUpdateContents(<BundleConfig>{
259 os: codePushRelaseParams.app.os,
260 projectRootPath: projectRootPath,
261 });
262 }).then((pathToUpdateContents: string) => {
263 p.report({ message: ACStrings.ArchivingUpdateContentsMessage });
264 updateContentsDirectory = pathToUpdateContents;
265 this.logger.log(`CodePush updated contents directory path: ${updateContentsDirectory}`, LogLevel.Debug);
266 return updateContents.zip(pathToUpdateContents, projectRootPath);
267 }).then((pathToZippedBundle: string) => {
268 p.report({ message: ACStrings.ReleasingUpdateContentsMessage });
269 codePushRelaseParams.updatedContentZipPath = pathToZippedBundle;
270 codePushRelaseParams.isMandatory = isMandatory;
271 return new Promise<any>((publishResolve, publishReject) => {
272 Auth.getProfile(projectRootPath)
273 .then((profile: Profile) => {
274 return profile.accessToken;
275 }).then((token: string) => {
276 codePushRelaseParams.token = token;
277 return CodePushRelease.exec(client, codePushRelaseParams, this.logger);
278 }).then((response: any) => publishResolve(response))
279 .catch((error: any) => publishReject(error));
280 });
281 }).then((response: any) => {
282 if (response.succeeded && response.result) {
283 VsCodeUtils.ShowInformationMessage(`Successfully released an update to the "${codePushRelaseParams.deploymentName}" deployment of the "${codePushRelaseParams.app.appName}" app`);
284 resolve(response.result);
285 } else {
286 VsCodeUtils.ShowErrorMessage(response.errorMessage);
287 }
288 fileUtils.rmDir(codePushRelaseParams.updatedContentZipPath);
289 }).catch((error: Error) => {
290 if (error && error.message) {
291 VsCodeUtils.ShowErrorMessage(`An error occured on doing Code Push release. ${error.message}`);
292 } else {
293 VsCodeUtils.ShowErrorMessage("An error occured on doing Code Push release");
294 }
295
296 fileUtils.rmDir(codePushRelaseParams.updatedContentZipPath);
297 });
298 });
299 });
300 }
301
302 public showMenu(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
303 return Auth.getProfile(appCenterManager.projectRootPath).then((profile: Profile | null) => {
304 let defaultApp: DefaultApp | null = null;
305 if (profile && profile.defaultApp) {
306 defaultApp = profile.defaultApp;
307 }
308 let menuPlaceHolederTitle = ACStrings.MenuTitlePlaceholder;
309 let appCenterMenuOptions = [
310 {
311 label: ACStrings.ReleaseReactMenuText(defaultApp),
312 description: "",
313 target: ACCommandNames.CodePushReleaseReact,
314 },
315 {
316 label: ACStrings.SetCurrentAppMenuText(defaultApp),
317 description: "",
318 target: ACCommandNames.SetCurrentApp,
319 },
320 {
321 label: ACStrings.LogoutMenuLabel,
322 description: "",
323 target: ACCommandNames.Logout,
324 },
325 ];
326
327 // This item is avaliable only if we have specified app already
328 if (defaultApp && defaultApp.currentAppDeployments) {
329 // Let logout command be always the last one in the list
330 appCenterMenuOptions.splice(appCenterMenuOptions.length - 1, 0,
331 {
332 label: ACStrings.SetCurrentAppDeploymentText(defaultApp),
333 description: "",
334 target: ACCommandNames.SetCurrentDeployment,
335 }
336 );
337 appCenterMenuOptions.splice(appCenterMenuOptions.length - 1, 0,
338 {
339 label: ACStrings.SetCurrentAppTargetBinaryVersionText(defaultApp),
340 description: "",
341 target: ACCommandNames.SetTargetBinaryVersionForRelease,
342 }
343 );
344 appCenterMenuOptions.splice(appCenterMenuOptions.length - 1, 0,
345 {
346 label: ACStrings.SetCurrentAppIsMandatoryText(defaultApp),
347 description: "",
348 target: ACCommandNames.SwitchMandatoryPropertyForRelease,
349 }
350 );
351 }
352
353 return vscode.window.showQuickPick(appCenterMenuOptions, { placeHolder: menuPlaceHolederTitle })
354 .then((selected: {label: string, description: string, target: string}) => {
355 if (!selected) {
356 // user cancel selection
357 return Q.resolve(void 0);
358 }
359 switch (selected.target) {
360 case (ACCommandNames.SetCurrentApp):
361 return this.setCurrentApp(client, appCenterManager);
362
363 case (ACCommandNames.SetCurrentDeployment):
364 return this.setCurrentDeployment(appCenterManager);
365
366 case (ACCommandNames.CodePushReleaseReact):
367 return this.releaseReact(client, appCenterManager);
368
369 case (ACCommandNames.SetTargetBinaryVersionForRelease):
370 return this.setTargetBinaryVersionForRelease(appCenterManager);
371
372 case (ACCommandNames.SwitchMandatoryPropertyForRelease):
373 return this.switchIsMandatoryForRelease(appCenterManager);
374
375 case (ACCommandNames.Logout):
376 return this.logout(appCenterManager);
377
378 default:
379 // Ideally shouldn't be there :)
380 this.logger.error("Unknown appcenter show menu command");
381 return Q.resolve(void 0);
382 }
383 });
384 });
385 }
386
387 public switchIsMandatoryForRelease(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
388 this.restoreCurrentApp(appCenterManager.projectRootPath).then((app: DefaultApp) => {
389 if (app) {
390 const newMandatoryValue = !!!app.isMandatory;
391 const osVal: AppCenterOS = AppCenterOS[app.os];
392 this.saveCurrentApp(
393 appCenterManager.projectRootPath,
394 app.identifier,
395 osVal, {
396 currentDeploymentName: app.currentAppDeployments.currentDeploymentName,
397 codePushDeployments: app.currentAppDeployments.codePushDeployments,
398 },
399 app.targetBinaryVersion,
400 newMandatoryValue
401 ).then(() => {
402 VsCodeUtils.ShowInformationMessage(`Changed release to ${newMandatoryValue ? "Mandatory" : "NOT Mandatory"}`);
403 });
404 } else {
405 VsCodeUtils.ShowInformationMessage(ACStrings.NoCurrentAppSetMsg);
406 }
407 });
408 return Q.resolve(void 0);
409 }
410
411 public setTargetBinaryVersionForRelease(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
412 vscode.window.showInputBox({ prompt: ACStrings.PleaseProvideTargetBinaryVersion, ignoreFocusOut: true })
413 .then(appVersion => {
414 if (appVersion === ACConstants.AppCenterDefaultTargetBinaryVersion || (appVersion && !!validRange(appVersion))) {
415 return this.restoreCurrentApp(appCenterManager.projectRootPath).then((app: DefaultApp) => {
416 if (app) {
417 return this.saveCurrentApp(
418 appCenterManager.projectRootPath,
419 app.identifier,
420 AppCenterOS[app.os], {
421 currentDeploymentName: app.currentAppDeployments.currentDeploymentName,
422 codePushDeployments: app.currentAppDeployments.codePushDeployments,
423 },
424 appVersion,
425 app.isMandatory
426 ).then(() => {
427 if (appVersion) {
428 VsCodeUtils.ShowInformationMessage(`Changed target binary version to '${appVersion}'`);
429 } else {
430 VsCodeUtils.ShowInformationMessage(`Changed target binary version to automatically fetched`);
431 }
432 });
433 } else {
434 VsCodeUtils.ShowInformationMessage(ACStrings.NoCurrentAppSetMsg);
435 return Q.resolve(void 0);
436 }
437 });
438 } else if (appVersion === undefined) {
439 // if user press esc do nothing then
440 return Q.resolve(void 0);
441 } else {
442 VsCodeUtils.ShowWarningMessage(ACStrings.InvalidAppVersionParamMsg);
443 return Q.resolve(void 0);
444 }
445 });
446 return Q.resolve(void 0);
447 }
448
449 private saveCurrentApp(projectRootPath: string,
450 currentAppName: string,
451 appOS: AppCenterOS,
452 currentAppDeployments: CurrentAppDeployments | null,
453 targetBinaryVersion: string,
454 isMandatory: boolean): Q.Promise<DefaultApp | null> {
455 const defaultApp = ACUtils.toDefaultApp(currentAppName, appOS, currentAppDeployments, targetBinaryVersion, isMandatory);
456 if (!defaultApp) {
457 VsCodeUtils.ShowWarningMessage(ACStrings.InvalidCurrentAppNameMsg);
458 return Q.resolve(null);
459 }
460
461 return Auth.getProfile(projectRootPath).then((profile: Profile | null) => {
462 if (profile) {
463 profile.defaultApp = defaultApp;
464 profile.save(projectRootPath);
465 return Q.resolve(defaultApp);
466 } else {
467 // No profile - not logged in?
468 VsCodeUtils.ShowWarningMessage(ACStrings.UserIsNotLoggedInMsg);
469 return Q.resolve(null);
470 }
471 });
472 }
473
474 private restoreCurrentApp(projectRootPath: string): Q.Promise<DefaultApp | null> {
475 return Auth.getProfile(projectRootPath).then((profile: Profile | null) => {
476 if (profile && profile.defaultApp) {
477 return Q.resolve(profile.defaultApp);
478 }
479 return Q.resolve(null);
480 });
481 }
482
483 private loginWithToken(token: string | undefined, appCenterManager: AppCenterExtensionManager) {
484 if (!token) {
485 return;
486 }
487 return Auth.doTokenLogin(token, appCenterManager.projectRootPath).then((profile: Profile) => {
488 if (!profile) {
489 this.logger.log("Failed to fetch user info from server", LogLevel.Error);
490 VsCodeUtils.ShowWarningMessage(ACStrings.FailedToExecuteLoginMsg);
491 return;
492 }
493 VsCodeUtils.ShowInformationMessage(ACStrings.YouAreLoggedInMsg(profile.displayName));
494 return appCenterManager.setupAppCenterStatusBar(profile);
495 });
496 }
497}