github-api-helper.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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. const IS_WINDOWS = process.platform === 'win32'
  12. export async function downloadRepository(
  13. authToken: string,
  14. owner: string,
  15. repo: string,
  16. ref: string,
  17. commit: string,
  18. repositoryPath: 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)
  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)
  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. ): Promise<string> {
  76. return await retryHelper.execute(async () => {
  77. core.info('Retrieving the default branch name')
  78. const octokit = new github.GitHub(authToken)
  79. let result: string
  80. try {
  81. // Get the default branch from the repo info
  82. const response = await octokit.repos.get({owner, repo})
  83. result = response.data.default_branch
  84. assert.ok(result, 'default_branch cannot be empty')
  85. } catch (err) {
  86. // Handle .wiki repo
  87. if (err['status'] === 404 && repo.toUpperCase().endsWith('.WIKI')) {
  88. result = 'master'
  89. }
  90. // Otherwise error
  91. else {
  92. throw err
  93. }
  94. }
  95. // Print the default branch
  96. core.info(`Default branch '${result}'`)
  97. // Prefix with 'refs/heads'
  98. if (!result.startsWith('refs/')) {
  99. result = `refs/heads/${result}`
  100. }
  101. return result
  102. })
  103. }
  104. async function downloadArchive(
  105. authToken: string,
  106. owner: string,
  107. repo: string,
  108. ref: string,
  109. commit: string
  110. ): Promise<Buffer> {
  111. const octokit = new github.GitHub(authToken)
  112. const params: Octokit.ReposGetArchiveLinkParams = {
  113. owner: owner,
  114. repo: repo,
  115. archive_format: IS_WINDOWS ? 'zipball' : 'tarball',
  116. ref: commit || ref
  117. }
  118. const response = await octokit.repos.getArchiveLink(params)
  119. if (response.status != 200) {
  120. throw new Error(
  121. `Unexpected response from GitHub API. Status: ${response.status}, Data: ${response.data}`
  122. )
  123. }
  124. return Buffer.from(response.data) // response.data is ArrayBuffer
  125. }