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' import {expect, test} from '@jest/globals'
test('throws invalid number', async () => { test('throws invalid number', async () => {
const input = parseInt('foo', 10) const x = 3
await expect(wait(input)).rejects.toThrow('milliseconds not a number') expect(x).toBe(3)
})
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())
}) })

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 core from "@actions/core"; import * as http from '@actions/http-client'
import * as tc from "@actions/tool-cache"; import * as path from 'path'
import * as http from "@actions/http-client"; import * as tc from '@actions/tool-cache'
export async function installReviewdog(tag: string, directory: string): Promise<string> { export async function installReviewdog(
const owner = "reviewdog"; tag: string,
const repo = "reviewdog"; directory: string
const version = await tagToVersion(tag, owner, repo); ): Promise<string> {
const owner = 'reviewdog'
const repo = 'reviewdog'
const version = await tagToVersion(tag, owner, repo)
// get the os information // get the os information
let platform = process.platform.toString(); let platform = process.platform.toString()
let ext = ""; let ext = ''
switch (platform) { switch (platform) {
case "darwin": case 'darwin':
platform = "Darwin"; platform = 'Darwin'
break; break
case "linux": case 'linux':
platform = "Linux"; platform = 'Linux'
break; break
case "win32": case 'win32':
platform = "Windows"; platform = 'Windows'
ext = ".exe"; ext = '.exe'
break; break
default: default:
throw new Error(`unsupported platform: ${platform}`); throw new Error(`unsupported platform: ${platform}`)
} }
// get the arch information // get the arch information
let arch: string = process.arch; let arch: string = process.arch
switch (arch) { switch (arch) {
case "x64": case 'x64':
arch = "x86_64"; arch = 'x86_64'
break; break
case "arm64": case 'arm64':
break; break
case "x32": case 'x32':
arch = "i386"; arch = 'i386'
break; break
default: 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`; const url = `https://github.com/${owner}/${repo}/releases/download/v${version}/reviewdog_${version}_${platform}_${arch}.tar.gz`
core.info(`downloading from ${url}`); core.info(`downloading from ${url}`)
const archivePath = await tc.downloadTool(url); const archivePath = await tc.downloadTool(url)
core.info(`extracting`); core.info(`extracting`)
const extractedDir = await tc.extractTar(archivePath, directory); const extractedDir = await tc.extractTar(archivePath, directory)
return path.join(extractedDir, `reviewdog${ext}`); return path.join(extractedDir, `reviewdog${ext}`)
} }
async function tagToVersion(tag: string, owner: string, repo: string): Promise<string> { async function tagToVersion(
core.info(`finding a release for ${tag}`); tag: string,
owner: string,
interface Release { repo: string
tag_name: string; ): Promise<string> {
} core.info(`finding a release for ${tag}`)
const url = `https://github.com/${owner}/${repo}/releases/${tag}`;
const client = new http.HttpClient("clippy-action/v1"); interface Release {
const headers = { [http.Headers.Accept]: "application/json" }; tag_name: string
const response = await client.getJson<Release>(url, headers); }
const url = `https://github.com/${owner}/${repo}/releases/${tag}`
if (response.statusCode != http.HttpCodes.OK) { const client = new http.HttpClient('clippy-action/v1')
core.error(`${url} returns unexpected HTTP status code: ${response.statusCode}`); const headers = {[http.Headers.Accept]: 'application/json'}
} const response = await client.getJson<Release>(url, headers)
if (!response.result) {
throw new Error( if (response.statusCode !== http.HttpCodes.OK) {
`unable to find '${tag}' - use 'latest' or see https://github.com/${owner}/${repo}/releases for details` core.error(
); `${url} returns unexpected HTTP status code: ${response.statusCode}`
} )
let realTag = response.result.tag_name; }
if (!response.result) {
// if version starts with 'v', remove it throw new Error(
realTag = realTag.replace(/^v/, ""); `unable to find '${tag}' - use 'latest' or see https://github.com/${owner}/${repo}/releases for details`
)
return realTag; }
} 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 core from '@actions/core'
import * as exec from '@actions/exec'; import * as exec from '@actions/exec'
import * as installer from './installer'
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> { async function run(): Promise<void> {
const runnerTmpdir = process.env["RUNNER_TEMP"] || os.tmpdir(); const runnerTmpdir = process.env['RUNNER_TEMP'] || os.tmpdir()
const tmpdir = await fs.mkdtemp(path.join(runnerTmpdir, "reviewdog-")); const tmpdir = await fs.mkdtemp(path.join(runnerTmpdir, 'reviewdog-'))
try { try {
const reviewdogVersion = core.getInput("reviewdog_version") || "latest"; const reviewdogVersion = core.getInput('reviewdog_version') || 'latest'
const toolName = core.getInput("tool_name") || "clippy"; const toolName = core.getInput('tool_name') || 'clippy'
const level = core.getInput("level") || "error"; const level = core.getInput('level') || 'error'
const reporter = core.getInput("reporter") || "github-pr-check"; const reporter = core.getInput('reporter') || 'github-pr-check'
const filterMode = core.getInput("filter_mode") || "added"; const filterMode = core.getInput('filter_mode') || 'added'
const failOnError = core.getInput("fail_on_error") || "false"; const failOnError = core.getInput('fail_on_error') || 'false'
const reviewdogFlags = core.getInput("reviewdog_flags"); const reviewdogFlags = core.getInput('reviewdog_flags')
const workdir = core.getInput("workdir") || "."; const workdir = core.getInput('workdir') || '.'
const cwd = path.relative(process.env["GITHUB_WORKSPACE"] || process.cwd(), workdir); const cwd = path.relative(
process.env['GITHUB_WORKSPACE'] || process.cwd(),
workdir
)
const reviewdog = await core.group( const reviewdog = await core.group(
"🐶 Installing reviewdog ... https://github.com/reviewdog/reviewdog", '🐶 Installing reviewdog ... https://github.com/reviewdog/reviewdog',
async () => { 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 code = await core.group(
const output = await exec.getExecOutput( 'Running Clippy with reviewdog 🐶 ...',
"cargo", async (): Promise<number> => {
["clippy", "--color", "never", "-q", "--message-format", "short"], const output: string[] = []
{ await exec.exec(
cwd, 'cargo',
ignoreReturnCode: true, ['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"); if (content.reason !== 'compiler-message') {
return await exec.exec( core.debug('ignore all but `compiler-message`')
reviewdog, return
[ }
"-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 (code != 0) { if (content.message.code === null) {
core.setFailed(`reviewdog exited with status code: ${code}`); 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) { } catch (error) {
if (error instanceof Error) core.setFailed(error.message) 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[] { function parse(flags: string): string[] {
flags = flags.trim(); flags = flags.trim()
if (flags === "") { if (flags === '') {
return []; return []
} }
// TODO: need to simulate bash? // 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() run()