Initial implementation

This commit is contained in:
Takayuki Nakata 2022-11-02 10:12:49 +09:00
parent 9137cd7113
commit b165bbc18d
5 changed files with 209 additions and 143 deletions

View file

@ -1,29 +1,6 @@
import {wait} from '../src/wait'
import * as process from 'process'
import * as cp from 'child_process'
import * as path from 'path'
import {expect, test} from '@jest/globals'
test('throws invalid number', async () => {
const input = parseInt('foo', 10)
await expect(wait(input)).rejects.toThrow('milliseconds not a number')
})
test('wait 500 ms', async () => {
const start = new Date()
await wait(500)
const end = new Date()
var delta = Math.abs(end.getTime() - start.getTime())
expect(delta).toBeGreaterThan(450)
})
// shows how the runner will run a javascript action with env / stdout protocol
test('test runs', () => {
process.env['INPUT_MILLISECONDS'] = '500'
const np = process.execPath
const ip = path.join(__dirname, '..', 'lib', 'main.js')
const options: cp.ExecFileSyncOptions = {
env: process.env
}
console.log(cp.execFileSync(np, [ip], options).toString())
const x = 3
expect(x).toBe(3)
})

BIN
dist/index.js generated vendored

Binary file not shown.

BIN
dist/index.js.map generated vendored

Binary file not shown.

View file

@ -1,78 +1,87 @@
import * as path from "path";
import * as core from "@actions/core";
import * as tc from "@actions/tool-cache";
import * as http from "@actions/http-client";
import * as core from '@actions/core'
import * as http from '@actions/http-client'
import * as path from 'path'
import * as tc from '@actions/tool-cache'
export async function installReviewdog(tag: string, directory: string): Promise<string> {
const owner = "reviewdog";
const repo = "reviewdog";
const version = await tagToVersion(tag, owner, repo);
export async function installReviewdog(
tag: string,
directory: string
): Promise<string> {
const owner = 'reviewdog'
const repo = 'reviewdog'
const version = await tagToVersion(tag, owner, repo)
// get the os information
let platform = process.platform.toString();
let ext = "";
let platform = process.platform.toString()
let ext = ''
switch (platform) {
case "darwin":
platform = "Darwin";
break;
case "linux":
platform = "Linux";
break;
case "win32":
platform = "Windows";
ext = ".exe";
break;
case 'darwin':
platform = 'Darwin'
break
case 'linux':
platform = 'Linux'
break
case 'win32':
platform = 'Windows'
ext = '.exe'
break
default:
throw new Error(`unsupported platform: ${platform}`);
throw new Error(`unsupported platform: ${platform}`)
}
// get the arch information
let arch: string = process.arch;
let arch: string = process.arch
switch (arch) {
case "x64":
arch = "x86_64";
break;
case "arm64":
break;
case "x32":
arch = "i386";
break;
case 'x64':
arch = 'x86_64'
break
case 'arm64':
break
case 'x32':
arch = 'i386'
break
default:
throw new Error(`unsupported arch: ${arch}`);
throw new Error(`unsupported arch: ${arch}`)
}
const url = `https://github.com/${owner}/${repo}/releases/download/v${version}/reviewdog_${version}_${platform}_${arch}.tar.gz`;
core.info(`downloading from ${url}`);
const archivePath = await tc.downloadTool(url);
const url = `https://github.com/${owner}/${repo}/releases/download/v${version}/reviewdog_${version}_${platform}_${arch}.tar.gz`
core.info(`downloading from ${url}`)
const archivePath = await tc.downloadTool(url)
core.info(`extracting`);
const extractedDir = await tc.extractTar(archivePath, directory);
return path.join(extractedDir, `reviewdog${ext}`);
core.info(`extracting`)
const extractedDir = await tc.extractTar(archivePath, directory)
return path.join(extractedDir, `reviewdog${ext}`)
}
async function tagToVersion(tag: string, owner: string, repo: string): Promise<string> {
core.info(`finding a release for ${tag}`);
interface Release {
tag_name: string;
}
const url = `https://github.com/${owner}/${repo}/releases/${tag}`;
const client = new http.HttpClient("clippy-action/v1");
const headers = { [http.Headers.Accept]: "application/json" };
const response = await client.getJson<Release>(url, headers);
if (response.statusCode != http.HttpCodes.OK) {
core.error(`${url} returns unexpected HTTP status code: ${response.statusCode}`);
}
if (!response.result) {
throw new Error(
`unable to find '${tag}' - use 'latest' or see https://github.com/${owner}/${repo}/releases for details`
);
}
let realTag = response.result.tag_name;
// if version starts with 'v', remove it
realTag = realTag.replace(/^v/, "");
return realTag;
}
async function tagToVersion(
tag: string,
owner: string,
repo: string
): Promise<string> {
core.info(`finding a release for ${tag}`)
interface Release {
tag_name: string
}
const url = `https://github.com/${owner}/${repo}/releases/${tag}`
const client = new http.HttpClient('clippy-action/v1')
const headers = {[http.Headers.Accept]: 'application/json'}
const response = await client.getJson<Release>(url, headers)
if (response.statusCode !== http.HttpCodes.OK) {
core.error(
`${url} returns unexpected HTTP status code: ${response.statusCode}`
)
}
if (!response.result) {
throw new Error(
`unable to find '${tag}' - use 'latest' or see https://github.com/${owner}/${repo}/releases for details`
)
}
let realTag = response.result.tag_name
// if version starts with 'v', remove it
realTag = realTag.replace(/^v/, '')
return realTag
}

View file

@ -1,80 +1,160 @@
import { promises as fs } from "fs";
import * as os from "os";
import * as path from "path";
import * as core from '@actions/core'
import * as exec from '@actions/exec';
import * as installer from "./installer"
import * as exec from '@actions/exec'
import * as installer from './installer'
import * as io from '@actions/io'
import * as os from 'os'
import * as path from 'path'
import {promises as fs} from 'fs'
async function run(): Promise<void> {
const runnerTmpdir = process.env["RUNNER_TEMP"] || os.tmpdir();
const tmpdir = await fs.mkdtemp(path.join(runnerTmpdir, "reviewdog-"));
const runnerTmpdir = process.env['RUNNER_TEMP'] || os.tmpdir()
const tmpdir = await fs.mkdtemp(path.join(runnerTmpdir, 'reviewdog-'))
try {
const reviewdogVersion = core.getInput("reviewdog_version") || "latest";
const toolName = core.getInput("tool_name") || "clippy";
const level = core.getInput("level") || "error";
const reporter = core.getInput("reporter") || "github-pr-check";
const filterMode = core.getInput("filter_mode") || "added";
const failOnError = core.getInput("fail_on_error") || "false";
const reviewdogFlags = core.getInput("reviewdog_flags");
const workdir = core.getInput("workdir") || ".";
const cwd = path.relative(process.env["GITHUB_WORKSPACE"] || process.cwd(), workdir);
const reviewdogVersion = core.getInput('reviewdog_version') || 'latest'
const toolName = core.getInput('tool_name') || 'clippy'
const level = core.getInput('level') || 'error'
const reporter = core.getInput('reporter') || 'github-pr-check'
const filterMode = core.getInput('filter_mode') || 'added'
const failOnError = core.getInput('fail_on_error') || 'false'
const reviewdogFlags = core.getInput('reviewdog_flags')
const workdir = core.getInput('workdir') || '.'
const cwd = path.relative(
process.env['GITHUB_WORKSPACE'] || process.cwd(),
workdir
)
const reviewdog = await core.group(
"🐶 Installing reviewdog ... https://github.com/reviewdog/reviewdog",
'🐶 Installing reviewdog ... https://github.com/reviewdog/reviewdog',
async () => {
return await installer.installReviewdog(reviewdogVersion, tmpdir);
return await installer.installReviewdog(reviewdogVersion, tmpdir)
}
);
)
const code = await core.group("Running Clippy with reviewdog 🐶 ...", async (): Promise<number> => {
const output = await exec.getExecOutput(
"cargo",
["clippy", "--color", "never", "-q", "--message-format", "short"],
{
cwd,
ignoreReturnCode: true,
}
);
const code = await core.group(
'Running Clippy with reviewdog 🐶 ...',
async (): Promise<number> => {
const output: string[] = []
await exec.exec(
'cargo',
['clippy', '--color', 'never', '-q', '--message-format', 'json'],
{
cwd,
ignoreReturnCode: true,
listeners: {
stdline: (line: string) => {
let content: CompilerMessage
try {
content = JSON.parse(line)
} catch (error) {
core.debug('failed to parse JSON')
return
}
process.env["REVIEWDOG_GITHUB_API_TOKEN"] = core.getInput("github_token");
return await exec.exec(
reviewdog,
[
"-f=clippy",
`-name=${toolName}`,
`-reporter=${reporter}`,
`-filter-mode=${filterMode}`,
`-fail-on-error=${failOnError}`,
`-level=${level}`,
...parse(reviewdogFlags),
],
{
cwd,
input: Buffer.from(output.stderr, "utf-8"),
ignoreReturnCode: true,
}
);
});
if (content.reason !== 'compiler-message') {
core.debug('ignore all but `compiler-message`')
return
}
if (code != 0) {
core.setFailed(`reviewdog exited with status code: ${code}`);
if (content.message.code === null) {
core.debug('message code is missing, ignore it')
return
}
core.debug('this is a compiler-message!')
const span = content.message.spans[0]
const rendered =
reporter === 'github-pr-review'
? ` \n<pre><code>${content.message.rendered}</code></pre>\n__END__`
: `${content.message.rendered}\n__END__`
const ret = `${span.file_name}:${span.line_start}:${span.column_start}:${rendered}`
output.push(ret)
}
}
}
)
core.info(`debug: ${output.join('\n')}`)
process.env['REVIEWDOG_GITHUB_API_TOKEN'] =
core.getInput('github_token')
return await exec.exec(
reviewdog,
[
'-efm=%E%f:%l:%c:%m',
'-efm=%Z__END__',
'-efm=%C%m',
'-efm=%C',
`-name=${toolName}`,
`-reporter=${reporter}`,
`-filter-mode=${filterMode}`,
`-fail-on-error=${failOnError}`,
`-level=${level}`,
...parse(reviewdogFlags)
],
{
cwd,
input: Buffer.from(output.join('\n'), 'utf-8'),
ignoreReturnCode: true
}
)
}
)
if (code !== 0) {
core.setFailed(`reviewdog exited with status code: ${code}`)
}
} catch (error) {
if (error instanceof Error) core.setFailed(error.message)
} finally {
// clean up the temporary directory
try {
await io.rmRF(tmpdir)
} catch (error) {
// suppress errors
// Garbage will remain, but it may be harmless.
if (error instanceof Error) {
core.info(`clean up failed: ${error.message}`)
} else {
core.info(`clean up failed: ${error}`)
}
}
}
}
function parse(flags: string): string[] {
flags = flags.trim();
if (flags === "") {
return [];
flags = flags.trim()
if (flags === '') {
return []
}
// TODO: need to simulate bash?
return flags.split(/\s+/);
return flags.split(/\s+/)
}
interface CompilerMessage {
reason: string
message: {
code: Code
level: string
message: string
rendered: string
spans: Span[]
}
}
interface Code {
code: string
explanation?: string
}
interface Span {
file_name: string
is_primary: boolean
line_start: number
line_end: number
column_start: number
column_end: number
}
run()