github-api-helper.ts 4.2 KB

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