microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.6.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appcenter/command/commandExecutor.ts

491lines · 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-node-sdk";
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 }
126 });
127 }
128 });
129 return Q.resolve(void 0);
130 }
131
132 public getCurrentApp(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
133 this.restoreCurrentApp(appCenterManager.projectRootPath).then((app: DefaultApp) => {
134 if (app) {
135 VsCodeUtils.ShowInformationMessage(ACStrings.YourCurrentAppMsg(app.identifier));
136 } else {
137 VsCodeUtils.ShowInformationMessage(ACStrings.NoCurrentAppSetMsg);
138 }
139 });
140 return Q.resolve(void 0);
141 }
142
143 public setCurrentApp(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
144 vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Get Apps"}, p => {
145 return new Promise((resolve, reject) => {
146 p.report({message: ACStrings.FetchAppsStatusBarMessage });
147 getQPromisifiedClientResult(client.account.apps.list()).then((apps: models.AppResponse[]) => {
148 const appsList: models.AppResponse[] = apps;
149 const reactNativeApps = appsList.filter(app => app.platform === ACConstants.AppCenterReactNativePlatformName);
150 resolve(reactNativeApps);
151 });
152 });
153 }).then((rnApps: models.AppResponse[]) => {
154 let options = rnApps.map(app => {
155 return {
156 label: `${app.name} (${app.os})`,
157 description: app.displayName,
158 target: app.name,
159 };
160 });
161 vscode.window.showQuickPick(options, { placeHolder: ACStrings.ProvideCurrentAppPromptMsg })
162 .then((selected: {label: string, description: string, target: string}) => {
163 if (selected) {
164 const selectedApps: models.AppResponse[] = rnApps.filter(app => app.name === selected.target);
165 if (selectedApps && selectedApps.length === 1) {
166 const selectedApp: models.AppResponse = selectedApps[0];
167 const selectedAppName: string = `${selectedApp.owner.name}/${selectedApp.name}`;
168 const OS: AppCenterOS = AppCenterOS[selectedApp.os.toLowerCase()];
169
170 vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Get Deployments"}, p => {
171 return new Promise((resolve, reject) => {
172 p.report({message: ACStrings.FetchDeploymentsStatusBarMessage });
173 getQPromisifiedClientResult(client.codepush.codePushDeployments.list(selectedApp.name, selectedApp.owner.name))
174 .then((deployments: models.Deployment[]) => {
175 resolve(deployments.sort((a, b): any => {
176 return a.name < b.name; // sort alphabetically
177 }));
178 });
179 });
180 })
181 .then((appDeployments: models.Deployment[]) => {
182 let currentDeployments: CurrentAppDeployments | null = null;
183 if (appDeployments.length > 0) {
184 const deployments: Deployment[] = appDeployments.map((d) => {
185 return {
186 name: d.name,
187 };
188 });
189 currentDeployments = {
190 codePushDeployments: deployments,
191 currentDeploymentName: appDeployments[0].name, // Select 1st one by default
192 };
193 }
194 this.saveCurrentApp(
195 appCenterManager.projectRootPath,
196 selectedAppName,
197 OS,
198 currentDeployments,
199 ACConstants.AppCenterDefaultTargetBinaryVersion,
200 ACConstants.AppCenterDefaultIsMandatoryParam)
201 .then((app: DefaultApp | null) => {
202 if (app) {
203 return VsCodeUtils.ShowInformationMessage(ACStrings.YourCurrentAppAndDeployemntMsg(selected.target
204 , app.currentAppDeployments.currentDeploymentName));
205 } else {
206 this.logger.error("Failed to save current app");
207 return Q.resolve(void 0);
208 }
209 });
210 });
211 }
212 }
213 });
214 });
215 return Q.resolve(void 0);
216 }
217
218 public releaseReact(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
219 let codePushRelaseParams = <ICodePushReleaseParams>{};
220 const projectRootPath: string = appCenterManager.projectRootPath;
221 return Q.Promise<any>((resolve, reject) => {
222 let updateContentsDirectory: string;
223 let isMandatory: boolean;
224 vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Get Apps" }, p => {
225 return new Promise<DefaultApp>((appResolve, appReject) => {
226 p.report({ message: ACStrings.GettingAppInfoMessage });
227 this.restoreCurrentApp(appCenterManager.projectRootPath)
228 .then((currentApp: DefaultApp) => appResolve(<DefaultApp>currentApp))
229 .catch(err => appReject(err));
230 }).then((currentApp: DefaultApp): any => {
231 p.report({ message: ACStrings.DetectingAppVersionMessage });
232 if (!currentApp) {
233 throw new Error(`No current app has been specified.`);
234 }
235 if (!currentApp.os || !reactNative.isValidOS(currentApp.os)) {
236 throw new Error(`OS must be "android", "ios", or "windows".`);
237 }
238 codePushRelaseParams.app = currentApp;
239 codePushRelaseParams.deploymentName = currentApp.currentAppDeployments.currentDeploymentName;
240 currentApp.os = currentApp.os.toLowerCase();
241 isMandatory = !!currentApp.isMandatory;
242 if (currentApp.targetBinaryVersion !== ACConstants.AppCenterDefaultTargetBinaryVersion) {
243 return currentApp.targetBinaryVersion;
244 } else {
245 switch (currentApp.os) {
246 case "android": return reactNative.getAndroidAppVersion(projectRootPath);
247 case "ios": return reactNative.getiOSAppVersion(projectRootPath);
248 case "windows": return reactNative.getWindowsAppVersion(projectRootPath);
249 default: throw new Error(`OS must be "android", "ios", or "windows".`);
250 }
251 }
252 }).then((appVersion: string) => {
253 p.report({ message: ACStrings.RunningReactNativeBundleCommandMessage });
254 codePushRelaseParams.appVersion = appVersion;
255 return reactNative.makeUpdateContents(<BundleConfig>{
256 os: codePushRelaseParams.app.os,
257 projectRootPath: projectRootPath,
258 });
259 }).then((pathToUpdateContents: string) => {
260 p.report({ message: ACStrings.ArchivingUpdateContentsMessage });
261 updateContentsDirectory = pathToUpdateContents;
262 this.logger.log(`CodePush updated contents directory path: ${updateContentsDirectory}`, LogLevel.Debug);
263 return updateContents.zip(pathToUpdateContents, projectRootPath);
264 }).then((pathToZippedBundle: string) => {
265 p.report({ message: ACStrings.ReleasingUpdateContentsMessage });
266 codePushRelaseParams.updatedContentZipPath = pathToZippedBundle;
267 codePushRelaseParams.isMandatory = isMandatory;
268 return new Promise<any>((publishResolve, publishReject) => {
269 Auth.getProfile(projectRootPath)
270 .then((profile: Profile) => {
271 return profile.accessToken;
272 }).then((token: string) => {
273 codePushRelaseParams.token = token;
274 return CodePushRelease.exec(client, codePushRelaseParams, this.logger);
275 }).then((response: any) => publishResolve(response))
276 .catch((error: any) => publishReject(error));
277 });
278 }).then((response: any) => {
279 if (response.succeeded && response.result) {
280 VsCodeUtils.ShowInformationMessage(`Successfully released an update to the "${codePushRelaseParams.deploymentName}" deployment of the "${codePushRelaseParams.app.appName}" app`);
281 resolve(response.result);
282 } else {
283 VsCodeUtils.ShowErrorMessage(response.errorMessage);
284 }
285 fileUtils.rmDir(codePushRelaseParams.updatedContentZipPath);
286 }).catch((error: Error) => {
287 if (error && error.message) {
288 VsCodeUtils.ShowErrorMessage(`An error occured on doing Code Push release. ${error.message}`);
289 } else {
290 VsCodeUtils.ShowErrorMessage("An error occured on doing Code Push release");
291 }
292
293 fileUtils.rmDir(codePushRelaseParams.updatedContentZipPath);
294 });
295 });
296 });
297 }
298
299 public showMenu(client: AppCenterClient, appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
300 return Auth.getProfile(appCenterManager.projectRootPath).then((profile: Profile | null) => {
301 let defaultApp: DefaultApp | null = null;
302 if (profile && profile.defaultApp) {
303 defaultApp = profile.defaultApp;
304 }
305 let menuPlaceHolederTitle = ACStrings.MenuTitlePlaceholder;
306 let appCenterMenuOptions = [
307 {
308 label: ACStrings.ReleaseReactMenuText(defaultApp),
309 description: "",
310 target: ACCommandNames.CodePushReleaseReact,
311 },
312 {
313 label: ACStrings.SetCurrentAppMenuText(defaultApp),
314 description: "",
315 target: ACCommandNames.SetCurrentApp,
316 },
317 {
318 label: ACStrings.LogoutMenuLabel,
319 description: "",
320 target: ACCommandNames.Logout,
321 },
322 ];
323
324 // This item is avaliable only if we have specified app already
325 if (defaultApp && defaultApp.currentAppDeployments) {
326 // Let logout command be always the last one in the list
327 appCenterMenuOptions.splice(appCenterMenuOptions.length - 1, 0,
328 {
329 label: ACStrings.SetCurrentAppDeploymentText(defaultApp),
330 description: "",
331 target: ACCommandNames.SetCurrentDeployment,
332 }
333 );
334 appCenterMenuOptions.splice(appCenterMenuOptions.length - 1, 0,
335 {
336 label: ACStrings.SetCurrentAppTargetBinaryVersionText(defaultApp),
337 description: "",
338 target: ACCommandNames.SetTargetBinaryVersionForRelease,
339 }
340 );
341 appCenterMenuOptions.splice(appCenterMenuOptions.length - 1, 0,
342 {
343 label: ACStrings.SetCurrentAppIsMandatoryText(defaultApp),
344 description: "",
345 target: ACCommandNames.SwitchMandatoryPropertyForRelease,
346 }
347 );
348 }
349
350 return vscode.window.showQuickPick(appCenterMenuOptions, { placeHolder: menuPlaceHolederTitle })
351 .then((selected: {label: string, description: string, target: string}) => {
352 if (!selected) {
353 // user cancel selection
354 return Q.resolve(void 0);
355 }
356 switch (selected.target) {
357 case (ACCommandNames.SetCurrentApp):
358 return this.setCurrentApp(client, appCenterManager);
359
360 case (ACCommandNames.SetCurrentDeployment):
361 return this.setCurrentDeployment(appCenterManager);
362
363 case (ACCommandNames.CodePushReleaseReact):
364 return this.releaseReact(client, appCenterManager);
365
366 case (ACCommandNames.SetTargetBinaryVersionForRelease):
367 return this.setTargetBinaryVersionForRelease(appCenterManager);
368
369 case (ACCommandNames.SwitchMandatoryPropertyForRelease):
370 return this.switchIsMandatoryForRelease(appCenterManager);
371
372 case (ACCommandNames.Logout):
373 return this.logout(appCenterManager);
374
375 default:
376 // Ideally shouldn't be there :)
377 this.logger.error("Unknown appcenter show menu command");
378 return Q.resolve(void 0);
379 }
380 });
381 });
382 }
383
384 public switchIsMandatoryForRelease(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
385 this.restoreCurrentApp(appCenterManager.projectRootPath).then((app: DefaultApp) => {
386 if (app) {
387 const newMandatoryValue = !!!app.isMandatory;
388 const osVal: AppCenterOS = AppCenterOS[app.os];
389 this.saveCurrentApp(
390 appCenterManager.projectRootPath,
391 app.identifier,
392 osVal, {
393 currentDeploymentName: app.currentAppDeployments.currentDeploymentName,
394 codePushDeployments: app.currentAppDeployments.codePushDeployments,
395 },
396 app.targetBinaryVersion,
397 newMandatoryValue
398 ).then(() => {
399 VsCodeUtils.ShowInformationMessage(`Changed release to ${newMandatoryValue ? "Mandotory" : "NOT Mandatory"}`);
400 });
401 } else {
402 VsCodeUtils.ShowInformationMessage(ACStrings.NoCurrentAppSetMsg);
403 }
404 });
405 return Q.resolve(void 0);
406 }
407
408 public setTargetBinaryVersionForRelease(appCenterManager: AppCenterExtensionManager): Q.Promise<void> {
409 vscode.window.showInputBox({ prompt: ACStrings.PleaseProvideTargetBinaryVersion, ignoreFocusOut: true })
410 .then(appVersion => {
411 if (appVersion === ACConstants.AppCenterDefaultTargetBinaryVersion || (appVersion && !!validRange(appVersion))) {
412 return this.restoreCurrentApp(appCenterManager.projectRootPath).then((app: DefaultApp) => {
413 if (app) {
414 return this.saveCurrentApp(
415 appCenterManager.projectRootPath,
416 app.identifier,
417 AppCenterOS[app.os], {
418 currentDeploymentName: app.currentAppDeployments.currentDeploymentName,
419 codePushDeployments: app.currentAppDeployments.codePushDeployments,
420 },
421 appVersion,
422 app.isMandatory
423 ).then(() => {
424 if (appVersion) {
425 VsCodeUtils.ShowInformationMessage(`Changed target binary version to '${appVersion}'`);
426 } else {
427 VsCodeUtils.ShowInformationMessage(`Changed target binary version to automatically fetched`);
428 }
429 });
430 } else {
431 VsCodeUtils.ShowInformationMessage(ACStrings.NoCurrentAppSetMsg);
432 return Q.resolve(void 0);
433 }
434 });
435 } else {
436 VsCodeUtils.ShowWarningMessage(ACStrings.InvalidAppVersionParamMsg);
437 return Q.resolve(void 0);
438 }
439 });
440 return Q.resolve(void 0);
441 }
442
443 private saveCurrentApp(projectRootPath: string,
444 currentAppName: string,
445 appOS: AppCenterOS,
446 currentAppDeployments: CurrentAppDeployments | null,
447 targetBinaryVersion: string,
448 isMandatory: boolean): Q.Promise<DefaultApp | null> {
449 const defaultApp = ACUtils.toDefaultApp(currentAppName, appOS, currentAppDeployments, targetBinaryVersion, isMandatory);
450 if (!defaultApp) {
451 VsCodeUtils.ShowWarningMessage(ACStrings.InvalidCurrentAppNameMsg);
452 return Q.resolve(null);
453 }
454
455 return Auth.getProfile(projectRootPath).then((profile: Profile | null) => {
456 if (profile) {
457 profile.defaultApp = defaultApp;
458 profile.save(projectRootPath);
459 return Q.resolve(defaultApp);
460 } else {
461 // No profile - not logged in?
462 VsCodeUtils.ShowWarningMessage(ACStrings.UserIsNotLoggedInMsg);
463 return Q.resolve(null);
464 }
465 });
466 }
467
468 private restoreCurrentApp(projectRootPath: string): Q.Promise<DefaultApp | null> {
469 return Auth.getProfile(projectRootPath).then((profile: Profile | null) => {
470 if (profile && profile.defaultApp) {
471 return Q.resolve(profile.defaultApp);
472 }
473 return Q.resolve(null);
474 });
475 }
476
477 private loginWithToken(token: string | undefined, appCenterManager: AppCenterExtensionManager) {
478 if (!token) {
479 return;
480 }
481 return Auth.doTokenLogin(token, appCenterManager.projectRootPath).then((profile: Profile) => {
482 if (!profile) {
483 this.logger.log("Failed to fetch user info from server", LogLevel.Error);
484 VsCodeUtils.ShowWarningMessage(ACStrings.FailedToExecuteLoginMsg);
485 return;
486 }
487 VsCodeUtils.ShowInformationMessage(ACStrings.YouAreLoggedInMsg(profile.displayName));
488 return appCenterManager.setupAppCenterStatusBar(profile);
489 });
490 }
491}