import type { WebSiteManagementModels } from '@azure/arm-appservice' ; import { HttpOperationResponse , ServiceClient , TokenCredentials } from '@azure/ms-rest-js' ; import { isArray } from 'util' ; import * as vscode from 'vscode' ; import { AzureWizard , createGenericClient , DialogResponses , IAzureQuickPickItem , IParsedError , openInPortal , parseError } from 'vscode-azureextensionui' ; import { ext } from '../extensionVariables' ; import { localize } from '../localize' ; import { ParsedSite } from '../SiteClient' ; import { nonNullProp } from '../utils/nonNull' ; import { openUrl } from '../utils/openUrl' ; import { verifyNoRunFromPackageSetting } from '../verifyNoRunFromPackageSetting' ; import { GitHubBranchListStep } from './GitHubBranchListStep' ; import { GitHubOrgListStep } from './GitHubOrgListStep' ; import { GitHubRepoListStep } from './GitHubRepoListStep' ; import { IConnectToGitHubWizardContext } from './IConnectToGitHubWizardContext' ; export type gitHubOrgData = { login : string ; repos_url : string ; } ; export type gitHubRepoData = { name : string ; repos_url : string ; url : string ; html_url : string ; } ; export type gitHubBranchData = { name : string ; } ; export type gitHubLink = { prev ? : string ; next ? : string ; last ? : string ; first ? : string ; } ; export async function connectToGitHub ( site , context ) { const title = localize ( 'connectGitHubRepo' , 'Connect GitHub repository' ) ; context . site = site ; const wizard = new AzureWizard ( context , { title , promptSteps : [ new GitHubOrgListStep ( ) , new GitHubRepoListStep ( ) , new GitHubBranchListStep ( ) ] } ) ; await wizard . prompt ( ) ; const siteSourceControl = { repoUrl : nonNullProp ( context , 'repoData' ) . html_url , branch : nonNullProp ( context , 'branchData' ) . name , isManualIntegration : false , deploymentRollbackEnabled : true , isMercurial : false } ; const repoName = `${ nonNullProp ( context , 'orgData' ) . login }/${ nonNullProp ( context , 'repoData' ) . name }` ; const client = await site . createClient ( context ) ; try { const connectingToGithub = localize ( 'ConnectingToGithub' , '"{0}" is being connected to repo "{1}". This may take several minutes...' , site . fullName , repoName ) ; const connectedToGithub = localize ( 'ConnectedToGithub' , 'Repo "{0}" is connected and deployed to "{1}".' , repoName , site . fullName ) ; await vscode . window . withProgress ( { location : vscode . ProgressLocation . Notification , title : connectingToGithub } , async ( ) : Promise < void > => { ext . outputChannel . appendLog ( connectingToGithub ) ; await verifyNoRunFromPackageSetting ( context , site ) ; await client . updateSourceControl ( siteSourceControl ) ; void vscode . window . showInformationMessage ( connectedToGithub ) ; ext . outputChannel . appendLog ( connectedToGithub ) ; } ) ; } catch ( err ) { try { await client . syncRepository ( ) ; } catch ( error ) { const parsedError = parseError ( error ) ; if ( parsedError . message . indexOf ( '"statusCode":200' ) === - 1 ) { throw error ; } } } } async function showGitHubAuthPrompt ( context , site ) { const invalidToken = localize ( 'tokenExpired' , 'Azure\'s GitHub token is invalid.  Authorize in the "Deployment Center"' ) ; const goToPortal = { title : localize ( 'goToPortal' , 'Go to Portal' ) } ; let input = DialogResponses . learnMore ; while ( input === DialogResponses . learnMore ) { input = await vscode . window . showErrorMessage ( invalidToken , { modal : true } , goToPortal , DialogResponses . learnMore ) ; if ( input === DialogResponses . learnMore ) { context . telemetry . properties . githubLearnMore = 'true' ; await openUrl ( 'https://aka.ms/B7g6sw' ) ; } } if ( input === goToPortal ) { context . telemetry . properties . githubGoToPortal = 'true' ; await openInPortal ( context , `${ site . id }/vstscd` ) ; } } export async function getGitHubJsonResponse < T > ( context , url ) { context . gitHubClient = context . gitHubClient || await createGitHubClient ( context ) ; try { const response = await context . gitHubClient . sendRequest ( { method : 'GET' , url } ) ; const headerLink = response . headers . get ( 'link' ) ; const nextLink = headerLink && parseLinkHeaderToGitHubLinkObject ( headerLink ) . next ; return [ < T > response . parsedBody , nextLink ] ; } catch ( error ) { const parsedError = parseError ( error ) ; if ( parsedError . message . indexOf ( 'Bad credentials' ) > - 1 ) { await showGitHubAuthPrompt ( context , nonNullProp ( context , 'site' ) ) ; context . errorHandling . suppressDisplay = true ; } throw error ; } } param label param description param data export function createQuickPickFromJsons < T > ( jsons , label ) { const quickPicks = [ ] ; if ( ! isArray ( jsons ) ) { jsons = [ jsons ] ; } for ( const json of jsons ) { if ( ! json [ label ] ) { continue ; } quickPicks . push ( { label : < string > json [ label ] , data : json } ) ; } return quickPicks ; } function parseLinkHeaderToGitHubLinkObject ( linkHeader ) { const linkUrls = linkHeader . split ( ', ' ) ; const linkMap = { } ; const relative = 'rel=' ; for ( const url of linkUrls ) { linkMap [ url . substring ( url . indexOf ( relative ) + relative . length + 1 , url . length - 1 ) ] = url . substring ( url . indexOf ( '<' ) + 1 , url . indexOf ( '>' ) ) ; } return linkMap ; } export interface ICachedQuickPicks < T > { picks : IAzureQuickPickItem < T > [ ] ; } export async function getGitHubQuickPicksWithLoadMore < T > ( context , cache , originalUrl , labelName , timeoutSeconds = 10 ) { const timeoutMs = timeoutSeconds * 1000 ; const startTime = Date . now ( ) ; let gitHubQuickPicks = [ ] ; let url = originalUrl ; do { const [ result , nextLink ] = await getGitHubJsonResponse < T [ ] > ( context , url ) ; gitHubQuickPicks = gitHubQuickPicks . concat ( result ) ; url = nextLink ; } while ( url && startTime + timeoutMs > Date . now ( ) ) ; cache . picks = cache . picks . concat ( createQuickPickFromJsons ( gitHubQuickPicks , labelName ) ) ; cache . picks . sort ( ( a , b ) => a . label . localeCompare ( b . label ) ) ; if ( url ) { return ( < IAzureQuickPickItem < T | string > [ ] > [ { label : '$(sync) Load More' , suppressPersistence : true , data : url } ] ) . concat ( cache . picks ) ; } else { return cache . picks ; } } export async function createGitHubClient ( context ) { const site = nonNullProp ( context , 'site' ) ; const client = await site . createClient ( context ) ; const oAuth2Token = ( await client . listSourceControls ( ) ) [ 0 ] . token ; if ( ! oAuth2Token ) { await showGitHubAuthPrompt ( context , site ) ; context . errorHandling . suppressDisplay = true ; const noToken = localize ( 'noToken' , 'No oAuth2 Token.' ) ; throw new Error ( noToken ) ; } return createGenericClient ( context , new TokenCredentials ( oAuth2Token ) ) ; }