Read-only mirror — the public repositories of the Forgejo instance, refreshed every 10 minutes. The live instance (fg.dieschoenewolke.de) is reachable only over the VPN; clone here with https://git.dieschoenewolke.de/git/<owner>/<repo>.git.
summaryrefslogtreecommitdiff
path: root/src/github-api-helper.ts
blob: e05e1e1246a47043ea49e23879e3287662860b88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import * as assert from 'assert'
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import * as fs from 'fs'
import * as github from '@actions/github'
import * as https from 'https'
import * as io from '@actions/io'
import * as path from 'path'
import * as refHelper from './ref-helper'
import * as retryHelper from './retry-helper'
import * as toolCache from '@actions/tool-cache'
import {ExecOptions} from '@actions/exec/lib/interfaces'
import {IncomingMessage} from 'http'
import {ReposGetArchiveLinkParams} from '@octokit/rest'
import {RequestOptions} from 'https'
import {WriteStream} from 'fs'

const IS_WINDOWS = process.platform === 'win32'

export async function downloadRepository(
  accessToken: string,
  owner: string,
  repo: string,
  ref: string,
  commit: string,
  repositoryPath: string
): Promise<void> {
  // Determine archive path
  const runnerTemp = process.env['RUNNER_TEMP'] as string
  assert.ok(runnerTemp, 'RUNNER_TEMP not defined')
  const archivePath = path.join(runnerTemp, 'checkout.tar.gz')
  // await fs.promises.writeFile(archivePath, raw)

  // Get the archive URL using the REST API
  await retryHelper.execute(async () => {
    // Prepare the archive stream
    core.debug(`Preparing the archive stream: ${archivePath}`)
    await io.rmRF(archivePath)
    const fileStream = fs.createWriteStream(archivePath)
    const fileStreamClosed = getFileClosedPromise(fileStream)

    try {
      // Get the archive URL using the GitHub REST API
      core.info('Getting archive URL from GitHub REST API')
      const octokit = new github.GitHub(accessToken)
      const params: RequestOptions & ReposGetArchiveLinkParams = {
        method: 'HEAD',
        archive_format: IS_WINDOWS ? 'zipball' : 'tarball',
        owner: owner,
        repo: repo,
        ref: refHelper.getDownloadRef(ref, commit)
      }
      const response = await octokit.repos.getArchiveLink(params)
      console.log('GOT THE RESPONSE')
      console.log(response.status)
      if (response.status != 302) {
        throw new Error(
          `Unexpected response from GitHub API. Status: '${response.status}'`
        )
      }
      console.log('GETTING THE LOCATION')
      const archiveUrl = response.headers['Location'] // Do not print the archive URL because it has an embedded token
      assert.ok(
        archiveUrl,
        `Expected GitHub API response to contain 'Location' header`
      )

      // Download the archive
      core.info('Downloading the archive') // Do not print the archive URL because it has an embedded token
      await downloadFile(archiveUrl, fileStream)
    } finally {
      fileStream.end()
      await fileStreamClosed
    }

    // return Buffer.from(response.data) // response.data is ArrayBuffer
  })

  // // Download the archive
  // core.info('Downloading the archive') // Do not print the URL since it contains a token to download the archive
  // await downloadFile(archiveUrl, archivePath)

  // // console.log(`status=${response.status}`)
  // // console.log(`headers=${JSON.stringify(response.headers)}`)
  // // console.log(`data=${response.data}`)
  // // console.log(`data=${JSON.stringify(response.data)}`)
  // // for (const key of Object.keys(response.data)) {
  // //   console.log(`data['${key}']=${response.data[key]}`)
  // // }

  // // Write archive to file
  // const runnerTemp = process.env['RUNNER_TEMP'] as string
  // assert.ok(runnerTemp, 'RUNNER_TEMP not defined')
  // const archivePath = path.join(runnerTemp, 'checkout.tar.gz')
  // await io.rmRF(archivePath)
  // await fs.promises.writeFile(archivePath, raw)
  // // await exec.exec(`ls -la "${archiveFile}"`, [], {
  // //   cwd: repositoryPath
  // // } as ExecOptions)

  // Extract archive
  const extractPath = path.join(
    runnerTemp,
    `checkout-archive${IS_WINDOWS ? '.zip' : '.tar.gz'}`
  )
  await io.rmRF(extractPath)
  await io.mkdirP(extractPath)
  if (IS_WINDOWS) {
    await toolCache.extractZip(archivePath, extractPath)
  } else {
    await toolCache.extractTar(archivePath, extractPath)
  }
  // await exec.exec(`tar -xzf "${archiveFile}"`, [], {
  //   cwd: extractPath
  // } as ExecOptions)

  // Determine the real directory to copy (ignore extra dir at root of the archive)
  const archiveFileNames = await fs.promises.readdir(extractPath)
  assert.ok(
    archiveFileNames.length == 1,
    'Expected exactly one directory inside archive'
  )
  const extraDirectoryName = archiveFileNames[0]
  core.info(`Resolved ${extraDirectoryName}`) // contains the short SHA
  const tempRepositoryPath = path.join(extractPath, extraDirectoryName)

  // Move the files
  for (const fileName of await fs.promises.readdir(tempRepositoryPath)) {
    const sourcePath = path.join(tempRepositoryPath, fileName)
    const targetPath = path.join(repositoryPath, fileName)
    await io.mv(sourcePath, targetPath)
  }

  await exec.exec(`find .`, [], {
    cwd: repositoryPath
  } as ExecOptions)
}

function downloadFile(url: string, fileStream: WriteStream): Promise<void> {
  return new Promise((resolve, reject) => {
    try {
      https.get(url, (response: IncomingMessage) => {
        if (response.statusCode != 200) {
          reject(`Request failed with status '${response.statusCode}'`)
          response.resume() // Consume response data to free up memory
          return
        }

        response.on('data', chunk => {
          fileStream.write(chunk)
        })
        response.on('end', () => {
          resolve()
        })
        response.on('error', err => {
          reject(err)
        })
      })
    } catch (err) {
      reject(err)
    }
  })
}

function getFileClosedPromise(stream: WriteStream): Promise<void> {
  return new Promise((resolve, reject) => {
    stream.on('error', err => {
      reject(err)
    })
    stream.on('finish', () => {
      resolve()
    })
  })
}