Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions __test__/utils.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as path from 'path'
import * as utils from '../lib/utils'

const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE']
const originalForgejoApiUrl = process.env['FORGEJO_API_URL']
const originalGitHubApiUrl = process.env['GITHUB_API_URL']

describe('utils tests', () => {
beforeAll(() => {
Expand All @@ -15,6 +17,8 @@ describe('utils tests', () => {
if (originalGitHubWorkspace) {
process.env['GITHUB_WORKSPACE'] = originalGitHubWorkspace
}
restoreEnvironmentVariable('FORGEJO_API_URL', originalForgejoApiUrl)
restoreEnvironmentVariable('GITHUB_API_URL', originalGitHubApiUrl)
})

test('getStringAsArray splits string input by newlines and commas', async () => {
Expand Down Expand Up @@ -69,6 +73,65 @@ describe('utils tests', () => {
)
})

test('determineApiBaseUrl uses the API URL supplied by the runner', async () => {
process.env['FORGEJO_API_URL'] = 'https://forgejo.example.com/api/v1/'
process.env['GITHUB_API_URL'] = 'https://forgejo.example.com/api/v1'
const probe = jest.fn()

await expect(
utils.determineApiBaseUrl('forgejo.example.com', probe)
).resolves.toEqual('https://forgejo.example.com/api/v1')
expect(probe).not.toHaveBeenCalled()
})

test('determineApiBaseUrl uses the GitHub API URL supplied by the runner', async () => {
delete process.env['FORGEJO_API_URL']
process.env['GITHUB_API_URL'] = 'https://github.example.com/api/v3'

await expect(
utils.determineApiBaseUrl('github.example.com')
).resolves.toEqual('https://github.example.com/api/v3')
})

test('determineApiBaseUrl returns the public GitHub API URL', async () => {
delete process.env['FORGEJO_API_URL']
delete process.env['GITHUB_API_URL']

await expect(utils.determineApiBaseUrl('github.com')).resolves.toEqual(
'https://api.github.com'
)
})

test('determineApiBaseUrl discovers the Forgejo API', async () => {
delete process.env['FORGEJO_API_URL']
delete process.env['GITHUB_API_URL']
const probe = jest.fn().mockResolvedValue({
ok: true,
headers: {get: () => 'application/json; charset=utf-8'}
})

await expect(
utils.determineApiBaseUrl('forgejo.example.com', probe)
).resolves.toEqual('https://forgejo.example.com/api/v1')
expect(probe).toHaveBeenCalledWith(
'https://forgejo.example.com/api/v1/version',
{signal: expect.any(AbortSignal)}
)
})

test('determineApiBaseUrl falls back to the GitHub Enterprise API', async () => {
delete process.env['FORGEJO_API_URL']
delete process.env['GITHUB_API_URL']
const probe = jest.fn().mockResolvedValue({
ok: false,
headers: {get: () => 'application/json'}
})

await expect(
utils.determineApiBaseUrl('github.example.com', probe)
).resolves.toEqual('https://github.example.com/api/v3')
})

test('secondsSinceEpoch returns the number of seconds since the Epoch', async () => {
const seconds = `${utils.secondsSinceEpoch()}`
expect(seconds.length).toEqual(10)
Expand Down Expand Up @@ -119,6 +182,17 @@ describe('utils tests', () => {
})
})

function restoreEnvironmentVariable(
name: string,
value: string | undefined
): void {
if (value === undefined) {
delete process.env[name]
} else {
process.env[name] = value
}
}

describe('retryWithBackoff', () => {
const makeConsistencyError = () => {
const error = new Error(
Expand Down
46 changes: 36 additions & 10 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,10 @@ function createPullRequest(inputs) {
core.startGroup('Determining the base and head repositories');
const baseRemote = gitConfigHelper.getGitRemote();
// Init the GitHub clients
const ghBranch = new github_helper_1.GitHubHelper(baseRemote.hostname, inputs.branchToken);
const ghPull = new github_helper_1.GitHubHelper(baseRemote.hostname, inputs.token);
const apiUrl = yield utils.determineApiBaseUrl(baseRemote.hostname);
core.info(`Using API base URL: ${apiUrl}`);
const ghBranch = new github_helper_1.GitHubHelper(apiUrl, inputs.branchToken);
const ghPull = new github_helper_1.GitHubHelper(apiUrl, inputs.token);
// Determine the head repository; the target for the pull request branch
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin';
const branchRepository = inputs.pushToFork
Expand Down Expand Up @@ -1384,17 +1386,12 @@ const ERROR_PR_REVIEW_TOKEN_SCOPE = 'Validation Failed: "Could not resolve to a
const ERROR_PR_FORK_COLLAB = `Fork collab can't be granted by someone without permission`;
const blobCreationLimit = (0, p_limit_1.default)(8);
class GitHubHelper {
constructor(githubServerHostname, token) {
constructor(apiUrl, token) {
const options = {};
if (token) {
options.auth = `${token}`;
}
if (githubServerHostname !== 'github.com') {
options.baseUrl = `https://${githubServerHostname}/api/v3`;
}
else {
options.baseUrl = 'https://api.github.com';
}
options.baseUrl = apiUrl;
options.throttle = octokit_client_1.throttleOptions;
options.retry = octokit_client_1.retryOptions;
this.octokit = new octokit_client_1.Octokit(options);
Expand Down Expand Up @@ -1462,7 +1459,8 @@ class GitHubHelper {
}
catch (e) {
const errorMessage = utils.getErrorMessage(e);
if (errorMessage.includes(ERROR_PR_ALREADY_EXISTS)) {
if (errorMessage.includes(ERROR_PR_ALREADY_EXISTS) ||
(e instanceof request_error_1.RequestError && e.status === 409)) {
core.info(`A pull request already exists for ${headBranch}`);
}
else if (errorMessage.includes(ERROR_PR_FORK_COLLAB)) {
Expand Down Expand Up @@ -1927,6 +1925,7 @@ exports.getStringAsArray = getStringAsArray;
exports.stripOrgPrefixFromTeams = stripOrgPrefixFromTeams;
exports.getRepoPath = getRepoPath;
exports.getRemoteUrl = getRemoteUrl;
exports.determineApiBaseUrl = determineApiBaseUrl;
exports.secondsSinceEpoch = secondsSinceEpoch;
exports.randomString = randomString;
exports.parseDisplayNameEmail = parseDisplayNameEmail;
Expand All @@ -1936,6 +1935,7 @@ exports.getErrorMessage = getErrorMessage;
exports.retryWithBackoff = retryWithBackoff;
const core = __importStar(__nccwpck_require__(7484));
const fs = __importStar(__nccwpck_require__(9896));
const proxy_1 = __nccwpck_require__(3459);
const path = __importStar(__nccwpck_require__(6928));
function getInputAsArray(name, options) {
return getStringAsArray(core.getInput(name, options));
Expand Down Expand Up @@ -1973,6 +1973,32 @@ function getRemoteUrl(protocol, hostname, repository) {
? `https://${hostname}/${repository}`
: `git@${hostname}:${repository}.git`;
}
function determineApiBaseUrl(hostname_1) {
return __awaiter(this, arguments, void 0, function* (hostname, probe = proxy_1.fetch) {
var _a;
const apiUrl = process.env['FORGEJO_API_URL'] || process.env['GITHUB_API_URL'];
if (apiUrl) {
return apiUrl.replace(/\/$/, '');
}
if (hostname === 'github.com') {
return 'https://api.github.com';
}
const forgejoApiUrl = `https://${hostname}/api/v1`;
try {
const response = yield probe(`${forgejoApiUrl}/version`, {
signal: AbortSignal.timeout(5000)
});
if (response.ok &&
((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('application/json'))) {
return forgejoApiUrl;
}
}
catch (error) {
core.debug(`API discovery failed: ${getErrorMessage(error)}`);
}
return `https://${hostname}/api/v3`;
});
}
function secondsSinceEpoch() {
const now = new Date();
return Math.round(now.getTime() / 1000);
Expand Down
6 changes: 4 additions & 2 deletions src/create-pull-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
core.startGroup('Determining the base and head repositories')
const baseRemote = gitConfigHelper.getGitRemote()
// Init the GitHub clients
const ghBranch = new GitHubHelper(baseRemote.hostname, inputs.branchToken)
const ghPull = new GitHubHelper(baseRemote.hostname, inputs.token)
const apiUrl = await utils.determineApiBaseUrl(baseRemote.hostname)
core.info(`Using API base URL: ${apiUrl}`)
const ghBranch = new GitHubHelper(apiUrl, inputs.branchToken)
const ghPull = new GitHubHelper(apiUrl, inputs.token)
// Determine the head repository; the target for the pull request branch
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
const branchRepository = inputs.pushToFork
Expand Down
13 changes: 6 additions & 7 deletions src/github-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,12 @@ type TreeObject = {
export class GitHubHelper {
private octokit: InstanceType<typeof Octokit>

constructor(githubServerHostname: string, token: string) {
constructor(apiUrl: string, token: string) {
const options: OctokitOptions = {}
if (token) {
options.auth = `${token}`
}
if (githubServerHostname !== 'github.com') {
options.baseUrl = `https://${githubServerHostname}/api/v3`
} else {
options.baseUrl = 'https://api.github.com'
}
options.baseUrl = apiUrl
options.throttle = throttleOptions
options.retry = retryOptions
this.octokit = new Octokit(options)
Expand Down Expand Up @@ -147,7 +143,10 @@ export class GitHubHelper {
}
} catch (e) {
const errorMessage = utils.getErrorMessage(e)
if (errorMessage.includes(ERROR_PR_ALREADY_EXISTS)) {
if (
errorMessage.includes(ERROR_PR_ALREADY_EXISTS) ||
(e instanceof RequestError && e.status === 409)
) {
core.info(`A pull request already exists for ${headBranch}`)
} else if (errorMessage.includes(ERROR_PR_FORK_COLLAB)) {
core.warning(
Expand Down
43 changes: 43 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as core from '@actions/core'
import * as fs from 'fs'
import {fetch} from 'node-fetch-native/proxy'
import * as path from 'path'

export function getInputAsArray(
Expand Down Expand Up @@ -51,6 +52,48 @@ export function getRemoteUrl(
: `git@${hostname}:${repository}.git`
}

interface ApiProbeResponse {
ok: boolean
headers: {
get(name: string): string | null
}
}

type ApiProbe = (
url: string,
options: {signal: AbortSignal}
) => Promise<ApiProbeResponse>

export async function determineApiBaseUrl(
hostname: string,
probe: ApiProbe = fetch
): Promise<string> {
const apiUrl = process.env['FORGEJO_API_URL'] || process.env['GITHUB_API_URL']
if (apiUrl) {
return apiUrl.replace(/\/$/, '')
}
if (hostname === 'github.com') {
return 'https://api.github.com'
}

const forgejoApiUrl = `https://${hostname}/api/v1`
try {
const response = await probe(`${forgejoApiUrl}/version`, {
signal: AbortSignal.timeout(5000)
})
if (
response.ok &&
response.headers.get('content-type')?.includes('application/json')
) {
return forgejoApiUrl
}
} catch (error) {
core.debug(`API discovery failed: ${getErrorMessage(error)}`)
}

return `https://${hostname}/api/v3`
}

export function secondsSinceEpoch(): number {
const now = new Date()
return Math.round(now.getTime() / 1000)
Expand Down