github-api-helper.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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 (
  88. (err as any)?.status === 404 &&
  89. repo.toUpperCase().endsWith('.WIKI')
  90. ) {
  91. result = 'master'
  92. }
  93. // Otherwise error
  94. else {
  95. throw err
  96. }
  97. }
  98. // Print the default branch
  99. core.info(`Default branch '${result}'`)
  100. // Prefix with 'refs/heads'
  101. if (!result.startsWith('refs/')) {
  102. result = `refs/heads/${result}`
  103. }
  104. return result
  105. })
  106. }
  107. async function downloadArchive(
  108. authToken: string,
  109. owner: string,
  110. repo: string,
  111. ref: string,
  112. commit: string
  113. ): Promise<Buffer> {
  114. const octokit = new github.GitHub(authToken)
  115. const params: Octokit.ReposGetArchiveLinkParams = {
  116. owner: owner,
  117. repo: repo,
  118. archive_format: IS_WINDOWS ? 'zipball' : 'tarball',
  119. ref: commit || ref
  120. }
  121. const response = await octokit.repos.getArchiveLink(params)
  122. if (response.status != 200) {
  123. throw new Error(
  124. `Unexpected response from GitHub API. Status: ${response.status}, Data: ${response.data}`
  125. )
  126. }
  127. return Buffer.from(response.data) // response.data is ArrayBuffer
  128. }