microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
6b8cdbb830ed7d91525cc76810df5458772836e7

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appcenter/command/commandExecutor.ts

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