github-api-helper.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import * as assert from 'assert'
  2. import * as core from '@actions/core'
  3. import * as exec from '@actions/exec'
  4. import * as fs from 'fs'
  5. import * as github from '@actions/github'
  6. import * as https from 'https'
  7. import * as io from '@actions/io'
  8. import * as path from 'path'
  9. import * as refHelper from './ref-helper'
  10. import * as retryHelper from './retry-helper'
  11. import * as toolCache from '@actions/tool-cache'
  12. import {ExecOptions} from '@actions/exec/lib/interfaces'
  13. import {IncomingMessage} from 'http'
  14. import {ReposGetArchiveLinkParams} from '@octokit/rest'
  15. import {RequestOptions} from 'https'
  16. import {WriteStream} from 'fs'
  17. const IS_WINDOWS = process.platform === 'win32'
  18. export async function downloadRepository(
  19. accessToken: string,
  20. owner: string,
  21. repo: string,
  22. ref: string,
  23. commit: string,
  24. repositoryPath: string
  25. ): Promise<void> {
  26. // Determine archive path
  27. const runnerTemp = process.env['RUNNER_TEMP'] as string
  28. assert.ok(runnerTemp, 'RUNNER_TEMP not defined')
  29. const archivePath = path.join(runnerTemp, 'checkout.tar.gz')
  30. // await fs.promises.writeFile(archivePath, raw)
  31. // Get the archive URL using the REST API
  32. await retryHelper.execute(async () => {
  33. // Prepare the archive stream
  34. core.debug(`Preparing the archive stream: ${archivePath}`)
  35. await io.rmRF(archivePath)
  36. const fileStream = fs.createWriteStream(archivePath)
  37. const fileStreamClosed = getFileClosedPromise(fileStream)
  38. try {
  39. // Get the archive URL using the GitHub REST API
  40. core.info('Getting archive URL from GitHub REST API')
  41. const octokit = new github.GitHub(accessToken)
  42. const params: RequestOptions & ReposGetArchiveLinkParams = {
  43. method: 'HEAD',
  44. archive_format: IS_WINDOWS ? 'zipball' : 'tarball',
  45. owner: owner,
  46. repo: repo,
  47. ref: refHelper.getDownloadRef(ref, commit)
  48. }
  49. const response = await octokit.repos.getArchiveLink(params)
  50. if (response.status != 302) {
  51. throw new Error(
  52. `Unexpected response from GitHub API. Status: '${response.status}'`
  53. )
  54. }
  55. const archiveUrl = response.headers['Location'] // Do not print the archive URL because it has an embedded token
  56. assert.ok(
  57. archiveUrl,
  58. `Expected GitHub API response to contain 'Location' header`
  59. )
  60. // Download the archive
  61. core.info('Downloading the archive') // Do not print the archive URL because it has an embedded token
  62. await downloadFile(archiveUrl, fileStream)
  63. } finally {
  64. await fileStreamClosed
  65. }
  66. // return Buffer.from(response.data) // response.data is ArrayBuffer
  67. })
  68. // // Download the archive
  69. // core.info('Downloading the archive') // Do not print the URL since it contains a token to download the archive
  70. // await downloadFile(archiveUrl, archivePath)
  71. // // console.log(`status=${response.status}`)
  72. // // console.log(`headers=${JSON.stringify(response.headers)}`)
  73. // // console.log(`data=${response.data}`)
  74. // // console.log(`data=${JSON.stringify(response.data)}`)
  75. // // for (const key of Object.keys(response.data)) {
  76. // // console.log(`data['${key}']=${response.data[key]}`)
  77. // // }
  78. // // Write archive to file
  79. // const runnerTemp = process.env['RUNNER_TEMP'] as string
  80. // assert.ok(runnerTemp, 'RUNNER_TEMP not defined')
  81. // const archivePath = path.join(runnerTemp, 'checkout.tar.gz')
  82. // await io.rmRF(archivePath)
  83. // await fs.promises.writeFile(archivePath, raw)
  84. // // await exec.exec(`ls -la "${archiveFile}"`, [], {
  85. // // cwd: repositoryPath
  86. // // } as ExecOptions)
  87. // Extract archive
  88. const extractPath = path.join(
  89. runnerTemp,
  90. `checkout-archive${IS_WINDOWS ? '.zip' : '.tar.gz'}`
  91. )
  92. await io.rmRF(extractPath)
  93. await io.mkdirP(extractPath)
  94. if (IS_WINDOWS) {
  95. await toolCache.extractZip(archivePath, extractPath)
  96. } else {
  97. await toolCache.extractTar(archivePath, extractPath)
  98. }
  99. // await exec.exec(`tar -xzf "${archiveFile}"`, [], {
  100. // cwd: extractPath
  101. // } as ExecOptions)
  102. // Determine the real directory to copy (ignore extra dir at root of the archive)
  103. const archiveFileNames = await fs.promises.readdir(extractPath)
  104. assert.ok(
  105. archiveFileNames.length == 1,
  106. 'Expected exactly one directory inside archive'
  107. )
  108. const extraDirectoryName = archiveFileNames[0]
  109. core.info(`Resolved ${extraDirectoryName}`) // contains the short SHA
  110. const tempRepositoryPath = path.join(extractPath, extraDirectoryName)
  111. // Move the files
  112. for (const fileName of await fs.promises.readdir(tempRepositoryPath)) {
  113. const sourcePath = path.join(tempRepositoryPath, fileName)
  114. const targetPath = path.join(repositoryPath, fileName)
  115. await io.mv(sourcePath, targetPath)
  116. }
  117. await exec.exec(`find .`, [], {
  118. cwd: repositoryPath
  119. } as ExecOptions)
  120. }
  121. function downloadFile(url: string, fileStream: WriteStream): Promise<void> {
  122. return new Promise((resolve, reject) => {
  123. try {
  124. https.get(url, (response: IncomingMessage) => {
  125. if (response.statusCode != 200) {
  126. reject(`Request failed with status '${response.statusCode}'`)
  127. response.resume() // Consume response data to free up memory
  128. return
  129. }
  130. response.on('data', chunk => {
  131. fileStream.write(chunk)
  132. })
  133. response.on('end', () => {
  134. resolve()
  135. })
  136. response.on('error', err => {
  137. reject(err)
  138. })
  139. // response.pipe(fileStream)
  140. })
  141. } catch (err) {
  142. reject(err)
  143. }
  144. })
  145. }
  146. function getFileClosedPromise(stream: WriteStream): Promise<void> {
  147. return new Promise((resolve, reject) => {
  148. stream.on('error', err => {
  149. reject(err)
  150. })
  151. stream.on('finish', () => {
  152. resolve()
  153. })
  154. })
  155. }