github-api-helper.ts
1 import * as assert from 'assert' 2 import * as core from '@actions/core' 3 import * as fs from 'fs' 4 import * as github from '@actions/github' 5 import * as io from '@actions/io' 6 import * as path from 'path' 7 import * as retryHelper from './retry-helper' 8 import * as toolCache from '@actions/tool-cache' 9 import {default as uuid} from 'uuid/v4' 10 import {Octokit} from '@octokit/rest' 11 12 const IS_WINDOWS = process.platform === 'win32' 13 14 export async function downloadRepository( 15 authToken: string, 16 owner: string, 17 repo: string, 18 ref: string, 19 commit: string, 20 repositoryPath: string 21 ): Promise<void> { 22 // Determine the default branch 23 if (!ref && !commit) { 24 core.info('Determining the default branch') 25 ref = await getDefaultBranch(authToken, owner, repo) 26 } 27 28 // Download the archive 29 let archiveData = await retryHelper.execute(async () => { 30 core.info('Downloading the archive') 31 return await downloadArchive(authToken, owner, repo, ref, commit) 32 }) 33 34 // Write archive to disk 35 core.info('Writing archive to disk') 36 const uniqueId = uuid() 37 const archivePath = path.join(repositoryPath, `${uniqueId}.tar.gz`) 38 await fs.promises.writeFile(archivePath, archiveData) 39 archiveData = Buffer.from('') // Free memory 40 41 // Extract archive 42 core.info('Extracting the archive') 43 const extractPath = path.join(repositoryPath, uniqueId) 44 await io.mkdirP(extractPath) 45 if (IS_WINDOWS) { 46 await toolCache.extractZip(archivePath, extractPath) 47 } else { 48 await toolCache.extractTar(archivePath, extractPath) 49 } 50 await io.rmRF(archivePath) 51 52 // Determine the path of the repository content. The archive contains 53 // a top-level folder and the repository content is inside. 54 const archiveFileNames = await fs.promises.readdir(extractPath) 55 assert.ok( 56 archiveFileNames.length == 1, 57 'Expected exactly one directory inside archive' 58 ) 59 const archiveVersion = archiveFileNames[0] // The top-level folder name includes the short SHA 60 core.info(`Resolved version ${archiveVersion}`) 61 const tempRepositoryPath = path.join(extractPath, archiveVersion) 62 63 // Move the files 64 for (const fileName of await fs.promises.readdir(tempRepositoryPath)) { 65 const sourcePath = path.join(tempRepositoryPath, fileName) 66 const targetPath = path.join(repositoryPath, fileName) 67 if (IS_WINDOWS) { 68 await io.cp(sourcePath, targetPath, {recursive: true}) // Copy on Windows (Windows Defender may have a lock) 69 } else { 70 await io.mv(sourcePath, targetPath) 71 } 72 } 73 await io.rmRF(extractPath) 74 } 75 76 /** 77 * Looks up the default branch name 78 */ 79 export async function getDefaultBranch( 80 authToken: string, 81 owner: string, 82 repo: string 83 ): Promise<string> { 84 return await retryHelper.execute(async () => { 85 core.info('Retrieving the default branch name') 86 const octokit = new github.GitHub(authToken) 87 let result: string 88 try { 89 // Get the default branch from the repo info 90 const response = await octokit.repos.get({owner, repo}) 91 result = response.data.default_branch 92 assert.ok(result, 'default_branch cannot be empty') 93 } catch (err) { 94 // Handle .wiki repo 95 if (err['status'] === 404 && repo.toUpperCase().endsWith('.WIKI')) { 96 result = 'master' 97 } 98 // Otherwise error 99 else { 100 throw err 101 } 102 } 103 104 // Print the default branch 105 core.info(`Default branch '${result}'`) 106 107 // Prefix with 'refs/heads' 108 if (!result.startsWith('refs/')) { 109 result = `refs/heads/${result}` 110 } 111 112 return result 113 }) 114 } 115 116 async function downloadArchive( 117 authToken: string, 118 owner: string, 119 repo: string, 120 ref: string, 121 commit: string 122 ): Promise<Buffer> { 123 const octokit = new github.GitHub(authToken) 124 const params: Octokit.ReposGetArchiveLinkParams = { 125 owner: owner, 126 repo: repo, 127 archive_format: IS_WINDOWS ? 'zipball' : 'tarball', 128 ref: commit || ref 129 } 130 const response = await octokit.repos.getArchiveLink(params) 131 if (response.status != 200) { 132 throw new Error( 133 `Unexpected response from GitHub API. Status: ${response.status}, Data: ${response.data}` 134 ) 135 } 136 137 return Buffer.from(response.data) // response.data is ArrayBuffer 138 }