diff --git a/src/calculate-download-checksum.ts b/src/calculate-download-checksum.ts index 5dd2e74..f6c6861 100644 --- a/src/calculate-download-checksum.ts +++ b/src/calculate-download-checksum.ts @@ -3,7 +3,7 @@ import { debug } from '@actions/core' import { URL } from 'url' import { createHash } from 'crypto' import { get as HTTP } from 'http' -import { get as HTTPS } from 'https' +import { get as HTTPS, request } from 'https' interface Headers { [name: string]: string @@ -32,27 +32,60 @@ function stream( }) } +type authInfo = { + token: string +} + +async function resolveRedirect( + apiClient: API, + url: URL, + asBinary: boolean +): Promise { + const authInfo = (await apiClient.auth()) as authInfo + return new Promise((resolve, reject) => { + const req = request( + url, + { + method: 'HEAD', + headers: { + authorization: authInfo.token ? `bearer ${authInfo.token}` : '', + accept: asBinary ? 'application/octet-stream' : '*/*', + 'User-Agent': 'bump-homebrew-formula-action', + }, + }, + (res) => { + if (res.statusCode == 302) { + const loc = res.headers['location'] + if (loc != null) { + resolve(new URL(loc)) + } else { + reject( + new Error(`got HTTP ${res.statusCode} but no Location header`) + ) + } + } else { + reject(new Error(`unexpected HTTP ${res.statusCode} response`)) + } + } + ) + req.end() + }) +} + async function resolveDownload(apiClient: API, url: URL): Promise { if (url.hostname == 'github.com') { const api = apiClient.rest const archive = parseArchiveUrl(url) if (archive != null) { - const { owner, repo, ref } = archive - const res = await (archive.ext == '.zip' - ? api.repos.downloadZipballArchive - : api.repos.downloadTarballArchive)({ - owner, - repo, - ref, - request: { - redirect: 'manual', - }, - }) - const loc = res.headers['location'] as string + const archiveType = archive.ext == '.zip' ? 'zipball' : 'tarball' + const endpoint = new URL( + `https://api.github.com/repos/${archive.owner}/${archive.repo}/${archiveType}/${archive.ref}` + ) + const loc = await resolveRedirect(apiClient, endpoint, false) // HACK: removing "legacy" from the codeload URL ensures that we get the // same archive file as web download. Otherwise, the downloaded archive // contains resolved commit SHA instead of the tag name in directory path. - return new URL(loc.replace('/legacy.', '/')) + return new URL(loc.href.replace('/legacy.', '/')) } const download = parseReleaseDownloadUrl(url) @@ -66,12 +99,7 @@ async function resolveDownload(apiClient: API, url: URL): Promise { `could not find asset '${download.name}' in '${tag}' release` ) } - const assetRes = await apiClient.request(asset.url, { - headers: { accept: 'application/octet-stream' }, - request: { redirect: 'manual' }, - }) - const loc = assetRes.headers['location'] as string - return new URL(loc) + return await resolveRedirect(apiClient, new URL(asset.url), true) } } return url diff --git a/src/main-test.ts b/src/main-test.ts index 0ce5759..822ea06 100644 --- a/src/main-test.ts +++ b/src/main-test.ts @@ -1,7 +1,6 @@ import test from 'ava' import api from './api' import { commitForRelease, prepareEdit } from './main' -import { Response } from 'node-fetch' test('commitForRelease()', (t) => { t.is( @@ -53,10 +52,10 @@ test('commitForRelease()', (t) => { test('prepareEdit() homebrew-core', async (t) => { const ctx = { sha: 'TAGSHA', - ref: 'refs/tags/v0.8.2', + ref: 'refs/tags/v1.9', repo: { - owner: 'OWNER', - repo: 'REPO', + owner: 'mislav', + repo: 'bump-homebrew-formula-action', }, } @@ -64,33 +63,19 @@ test('prepareEdit() homebrew-core', async (t) => { process.env['INPUT_HOMEBREW-TAP'] = 'Homebrew/homebrew-core' process.env['INPUT_COMMIT-MESSAGE'] = 'Upgrade {{formulaName}} to {{version}}' - // FIXME: this tests results in a live HTTP request. Figure out how to stub the `stream()` method in - // calculate-download-checksum. + // FIXME: this tests results in a live HTTP request. Figure out how to stub + // `stream()` and `resolveRedirect()` methods in calculate-download-checksum. const stubbedFetch = function (url: string) { - if ( - url == - 'https://api.github.com/repos/OWNER/REPO/tarball/refs%2Ftags%2Fv0.8.2' - ) { - return Promise.resolve( - new Response('', { - status: 301, - headers: { - Location: - 'https://github.com/mislav/bump-homebrew-formula-action/archive/v1.9.tar.gz', - }, - }) - ) - } throw url } - const apiClient = api('ATOKEN', { fetch: stubbedFetch, logRequests: false }) + const apiClient = api('', { fetch: stubbedFetch, logRequests: false }) const opts = await prepareEdit(ctx, apiClient, apiClient) t.is(opts.owner, 'Homebrew') t.is(opts.repo, 'homebrew-core') t.is(opts.branch, '') - t.is(opts.filePath, 'Formula/r/repo.rb') - t.is(opts.commitMessage, 'Upgrade repo to 0.8.2') + t.is(opts.filePath, 'Formula/b/bump-homebrew-formula-action.rb') + t.is(opts.commitMessage, 'Upgrade bump-homebrew-formula-action to 1.9') const oldFormula = ` class MyProgram < Formula @@ -104,7 +89,7 @@ test('prepareEdit() homebrew-core', async (t) => { t.is( ` class MyProgram < Formula - url "https://github.com/OWNER/REPO/archive/refs/tags/v0.8.2.tar.gz" + url "https://github.com/mislav/bump-homebrew-formula-action/archive/refs/tags/v1.9.tar.gz" sha256 "c036fbc44901b266f6d408d6ca36ba56f63c14cc97994a935fb9741b55edee83" head "git://example.com/repo.git", revision: "GITSHA"