mirror of
https://github.com/giraffate/clippy-action.git
synced 2024-11-21 15:39:32 +01:00
Initial implementation
This commit is contained in:
parent
9137cd7113
commit
b165bbc18d
5 changed files with 209 additions and 143 deletions
|
@ -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
BIN
dist/index.js
generated
vendored
Binary file not shown.
BIN
dist/index.js.map
generated
vendored
BIN
dist/index.js.map
generated
vendored
Binary file not shown.
135
src/installer.ts
135
src/installer.ts
|
@ -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
|
||||||
|
}
|
||||||
|
|
190
src/main.ts
190
src/main.ts
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue