diff --git a/.gitignore b/.gitignore index f7340dd..5cec262 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,3 @@ node_modules .idea # Ignore built ts files __tests__/runner/* -lib/**/* - diff --git a/hack/run-e2e-tests.sh b/hack/run-e2e-tests.sh index edb9f90..0af6eb2 100755 --- a/hack/run-e2e-tests.sh +++ b/hack/run-e2e-tests.sh @@ -2,9 +2,11 @@ set -euo pipefail +[[ -z $(command -v act) ]] && echo "act not found, install https://github.com/nektos/act" && exit 1 +[[ -z ${GITHUB_TOKEN} ]] && echo "GITHUB_TOKEN env variable is required. obtail one from https://github.com/settings/tokens/new" && exit 1 + yarn build -yarn package act pull_request -e tests/act-pull-request.json \ - -s GITHUB_TOKEN=${GITHUB_TOKEN} \ - --env CODEBALL_API_HOST=http://host.docker.internal:8080 \ No newline at end of file + -s GITHUB_TOKEN=${GITHUB_TOKEN} \ + --env CODEBALL_API_HOST=http://host.docker.internal:8080 diff --git a/package.json b/package.json index 638faee..3603543 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,16 @@ "description": "", "main": "lib/main.js", "scripts": { - "build": "tsc", + "build": "yarn baller:build && yarn approver:build && yarn status:build && yarn labeler:build", "format": "prettier --write '**/*.ts'", "format-check": "prettier --check '**/*.ts'", "lint": "eslint src/**/*.ts", - "package-baller": "ncc build lib/baller/main.js --out dist/baller --source-map --license licenses.txt", - "package-approver": "ncc build lib/approver/main.js --out dist/approver --source-map --license licenses.txt", - "package-status": "ncc build lib/status/main.js --out dist/status --source-map --license licenses.txt", - "package-labeler": "ncc build lib/labeler/main.js --out dist/labeler --source-map --license licenses.txt", - "package": "yarn package-baller && yarn package-approver && yarn package-status && yarn package-labeler", + "baller:build": "ncc build src/baller/main.ts --out dist/baller --source-map --license licenses.txt", + "approver:build": "ncc build src/approver/main.ts --out dist/approver --source-map --license licenses.txt", + "status:build": "ncc build src/status/main.ts --out dist/status --source-map --license licenses.txt", + "labeler:build": "ncc build src/labeler/main.ts --out dist/labeler --source-map --license licenses.txt", "test": "jest", - "all": "yarn build && yarn format && yarn lint && yarn package" + "all": "yarn format && yarn lint && yarn build" }, "repository": { "type": "git", diff --git a/src/approver/main.ts b/src/approver/main.ts index 1d100f9..db1e367 100644 --- a/src/approver/main.ts +++ b/src/approver/main.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core' import * as github from '@actions/github' -import {Octokit} from './octokit' +import {Octokit} from '../lib' async function run(): Promise { try { diff --git a/src/baller/main.ts b/src/baller/main.ts index 260da66..6e6ca70 100644 --- a/src/baller/main.ts +++ b/src/baller/main.ts @@ -1,16 +1,9 @@ -import fetch from 'node-fetch' import * as core from '@actions/core' import * as github from '@actions/github' - -type JobResponse = { - id: string - status: string -} +import {create} from '../lib' async function run(): Promise { try { - const hostName = process.env.CODEBALL_API_HOST || 'https://api.codeball.ai' - const pullRequestURL = github.context.payload?.pull_request?.html_url if (!pullRequestURL) { core.setFailed('No pull request URL found') @@ -25,20 +18,13 @@ async function run(): Promise { core.info(`Found contribution: ${pullRequestURL}`) - const data = { + const job = await create({ url: pullRequestURL, access_token: githubToken - } - - const response = await fetch(`${hostName}/jobs`, { - method: 'POST', - body: JSON.stringify(data) }) - const resData = (await response.json()) as JobResponse - - core.info(`Job created: ${resData.id}`) - core.setOutput('codeball-job-id', resData.id) + core.info(`Job created: ${job.id}`) + core.setOutput('codeball-job-id', job.id) } catch (error) { if (error instanceof Error) core.setFailed(error.message) } diff --git a/src/labeler/main.ts b/src/labeler/main.ts index 88b3cd7..9f379f5 100644 --- a/src/labeler/main.ts +++ b/src/labeler/main.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core' import * as github from '@actions/github' -import {Octokit} from './octokit' +import {Octokit} from '../lib' async function run(): Promise { try { diff --git a/src/labeler/octokit.ts b/src/labeler/octokit.ts deleted file mode 100644 index 271eae6..0000000 --- a/src/labeler/octokit.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {Octokit as Core} from '@octokit/core' -import {paginateRest} from '@octokit/plugin-paginate-rest' -import {legacyRestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods' -import ProxyAgent from 'proxy-agent' - -const DEFAULTS = { - baseUrl: getApiBaseUrl() -} - -export const Octokit = Core.plugin( - paginateRest, - legacyRestEndpointMethods -).defaults(function buildDefaults(options: any): any { - return { - ...DEFAULTS, - ...options, - request: { - agent: new ProxyAgent(), - ...options.request - } - } -}) - -// export type OC = InstanceType - -function getApiBaseUrl(): string { - /* istanbul ignore next */ - return process.env['GITHUB_API_URL'] || 'https://api.github.com' -} diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts new file mode 100644 index 0000000..61dde34 --- /dev/null +++ b/src/lib/api/index.ts @@ -0,0 +1,48 @@ +import fetch, {Response} from 'node-fetch' + +const BASE_URL = process.env.CODEBALL_API_HOST || 'https://api.codeball.ai' + +export class BadRequestError extends Error { + constructor(message: string) { + super(message); + this.name = 'BadRequestError'; + } +} + +export class NotFoundError extends Error { + constructor() { + super('Not found'); + this.name = 'NotFoundError'; + } +} + +const handleResponse = async (response: Response): Promise => { + if (response.ok) { + return await response.json(); + } else if (response.status === 404) { + throw new NotFoundError(); + } else if (response.status === 400) { + throw new BadRequestError(await response.text()); + } else { + throw new Error(await response.text()); + } +}; + +export const get = async (path: string) => + fetch(new URL(path, BASE_URL).toString(), { + headers: { + 'User-Agent': 'github-actions', + }, + redirect: 'follow', + }).then(handleResponse); + +export const post = async (path: string, body: any) => + fetch(new URL(path, BASE_URL).toString(), { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'User-Agent': 'github-actions', + 'Content-Type': 'application/json' + }, + redirect: 'follow', + }).then(handleResponse); diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..af44c56 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1,2 @@ +export * from './jobs' +export * from './octokit' diff --git a/src/lib/jobs/api.ts b/src/lib/jobs/api.ts new file mode 100644 index 0000000..83f4119 --- /dev/null +++ b/src/lib/jobs/api.ts @@ -0,0 +1,6 @@ +import { get as apiGET, post } from '../api'; +import type { Job } from './types'; + +export const get = (id: string): Promise => apiGET(`/jobs/${id}`); + +export const create = ({ url, access_token }: { url: string, access_token: string }): Promise => post('/jobs', { url, access_token }); diff --git a/src/lib/jobs/index.ts b/src/lib/jobs/index.ts new file mode 100644 index 0000000..aac8e2b --- /dev/null +++ b/src/lib/jobs/index.ts @@ -0,0 +1,3 @@ +export * from './types'; +export * from './utils'; +export * from './api'; diff --git a/src/approver/types.ts b/src/lib/jobs/types.ts similarity index 100% rename from src/approver/types.ts rename to src/lib/jobs/types.ts diff --git a/src/approver/utils.ts b/src/lib/jobs/utils.ts similarity index 100% rename from src/approver/utils.ts rename to src/lib/jobs/utils.ts diff --git a/src/approver/octokit.ts b/src/lib/octokit/index.ts similarity index 100% rename from src/approver/octokit.ts rename to src/lib/octokit/index.ts diff --git a/src/status/main.ts b/src/status/main.ts index 0928759..484e30a 100644 --- a/src/status/main.ts +++ b/src/status/main.ts @@ -1,14 +1,6 @@ -import fetch from 'node-fetch' -import {Job} from './types' -import {isContributionJob, isFinalStatus} from './utils' +import {isContributionJob, isFinalStatus, get} from '../lib' import * as core from '@actions/core' -async function getJob(id: string): Promise { - const res = await fetch(`https://api.codeball.ai/jobs/${id}`) - const data = (await res.json()) as Job - return data -} - async function run(): Promise { try { const jobID = core.getInput('codeball-job-id') @@ -18,7 +10,7 @@ async function run(): Promise { core.info(`Job ID: ${jobID}`) - let job = await getJob(jobID) + let job = await get(jobID) let attempts = 0 const maxAttempts = 60 while (attempts < maxAttempts && !isFinalStatus(job.status)) { @@ -27,7 +19,7 @@ async function run(): Promise { `Waiting for job ${jobID} to complete... (${attempts}/${maxAttempts})` ) await new Promise(resolve => setTimeout(resolve, 5000)) - job = await getJob(jobID) + job = await get(jobID) } if (!isFinalStatus(job.status)) { diff --git a/src/status/types.ts b/src/status/types.ts deleted file mode 100644 index 1f74caf..0000000 --- a/src/status/types.ts +++ /dev/null @@ -1,48 +0,0 @@ -export type Status = - | 'registered' - | 'running' - | 'failure' - | 'success' - | 'unknown' - -export type Job = { - id: string - created_at: string - started_at: string - completed_at: any - status: Status - error: any - repository?: Repository - contribution?: Contribution -} - -export type Repository = { - url: string - name: string - contribution_jobs: ContributionJob[] -} - -export type ContributionJob = { - id: string - parent: string - created_at: string - started_at: string - completed_at: string - status: Status - error?: Error - contribution: Contribution -} - -export type Error = { - message: string -} - -export type Contribution = { - url: string - number: number - title: string - merged_without_objections: boolean - created_at: string - merged_at: any - result: 'inconclusive' | 'approved' | 'not_approved' | null -} diff --git a/src/status/utils.ts b/src/status/utils.ts deleted file mode 100644 index d3744a0..0000000 --- a/src/status/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type {Status, Job} from './types' - -export const isFinalStatus = (st: Status): Boolean => - st === 'failure' || st === 'success' - -export const isContributionJob = (job: Job): Boolean => - job.contribution !== undefined diff --git a/tsconfig.json b/tsconfig.json index 17f434a..3d1b548 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,12 @@ { "compilerOptions": { - "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "outDir": "./lib", /* Redirect output structure to the directory. */ - "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "target": "es2015", + "moduleResolution": "node", + "module": "commonjs", + "rootDir": "./src", + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true }, "exclude": ["node_modules", "src/**/*.test.ts"] -} \ No newline at end of file +}