From b165bbc18d4d2208a24d3c846d3a1f6366f9ac68 Mon Sep 17 00:00:00 2001 From: Takayuki Nakata Date: Wed, 2 Nov 2022 10:12:49 +0900 Subject: [PATCH] Initial implementation --- __tests__/main.test.ts | 27 +----- dist/index.js | Bin 237085 -> 239323 bytes dist/index.js.map | Bin 275800 -> 278242 bytes src/installer.ts | 135 +++++++++++++++-------------- src/main.ts | 190 +++++++++++++++++++++++++++++------------ 5 files changed, 209 insertions(+), 143 deletions(-) diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 98f44c5..83c3489 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -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) }) diff --git a/dist/index.js b/dist/index.js index a01ce689cecf95b849c0c38d2133c73779a6f8d2..df63f30c89e625b6800dd4cfda0cacd856a040da 100644 GIT binary patch delta 2195 zcma)8TTB#J7|t1xT`mE+t?a_eSztYjVHUh}V^`LSfE2NSmr5hz?67-w8JV3KW@fpl zsA-d$G)=@F<4M}|_SU3*!1&NKwM|XiG;PvG`_Kn#OnozHW8}rAiHSXPfmPGAB@3DP z|L_0)`_I06t@76&E0_B`^tXDhi`?wE$#H8w5ZQX3L@{721`+&^RkoNEN}N9SyJ-zw#JM0+t03o z8Eo6?nv6+GTAecxQOg=UD2nGXvQTZLw<1RFm0eti=40CtIaBU0Me{pi0()^;L_KF| zZj^6k7>kc#JV@HMBjzw%?XB2cRD)8oq-hJhm@*|*>73ny$l0pxJ|t?IETu$Sc*0N> z1Q&OfQE&

9E0-kb70TucGyzfg*(3$;C&tS07gYR6$lXBy69p7zv2L(nP&+(*8X{RVv0)-|bE%FT zZS*%FQ&nX_5p&oDI2kyA_Zxk+NXD~Rc9mGyRo&6uZuDDTG*vgT?y9qrIhCK}HJwjQ z;ncJPY;S7xql_don2=M`tA50E8mAq^OjA=M%7~IIsEXjYaDvTRA=E&N zN|jkn*rUPVbyI3iV}v^IrcbYEAWYZXg`}*a>KKZ$3@7s@ zOg@WG{y*QrRo||gXo}4iOt1))yeyL~^KSTSj$2$HD=Gs@2Fyu@At~7o=td-&|Lu8= ztVP78fZ5rqEXK0!#@ zM3Fd5{)=e={ZzO^Ivdc0Wocy-WgCv|JF*yeCDtqphAHainuzU6KBp9ftKpH*&+f$T z9s(uasFpXTB08o$G0v8I>AA}wLc6?duwB4%<$tc~i@q(G#kqM9?vI z>PYiKJ-x7p^R{ATb{rk-KQh=q);BUBxQC!;bWk`vGSokeCSZb8-}D98NJg{R98?%l z#4WgBs?%7px>@Jkhk3ZA7V$WfON9HwnRr-^hg0!zj^5qP)l&Egx31Dr9(BuGP?S1~ z-Y3BxS7xKEDIh<5CeE=FE$R!9T}GTTRegb6f1|xpQlonA;NFOt)0l!D zcnHGe_Cf>Qbx%p|9EDt3Unsn`Gk##lc z`llsr#|rIZNza2`+R{*3_RvD158J~=>BHDl%gXk)m(juK(_rjrV~kx{Dh(3|oqNCU zp8K8e9R2*X|K>OS4@*%mSBMnxyRjnv7M(^GE#gRQM+S#94bswS4QAtF_-NE}ndlIk zigV&V27Vj+G6I0Lo^Ckn>lnd1La-KqC=4S{AFwtY@kHD*w&(0mv0(!h}Uksdhlpp+B;6*?BHSwYw_EGQrkAm;z9!lfvO;}xDS}Lz9Yvsii zzz5@-gRec4A)n{j_AW-oy<;eJS*e<8jW>2O zD<_~uoo(HCrC}QI`NSx)?4cnzxv+L>{e5L)dA0K4%KY&%;N9$^0QhIN^ae;`&O6jE zl)TTs7ovD&qQ!xKC#LoRg}Xv`oi>==V0=o?8N}2@K1AAdjj%HRE_a|DXsT1AJm1-{ z|2za))~Lz7;-SS2>;&>>0a7QftJa8IrA}RC{+_$VZG=t9`(soX!#lau_53In#3AqZ zL1DyOtVCkRh}Ap`tL2Xt${VHSlS)|H{K|r|wp=MM!6uieQHS{A7A!6~2SGa&Mz;z7 zi}t1}eIQro!UjutMjXYRxBn3&Z09x;F4MX7TuXyhu`{gAcy^2)ggM4{cy1FX~{Ed9ID|LNv{y(Q2Rqy}+ diff --git a/dist/index.js.map b/dist/index.js.map index d3cf1673c1ddb314d02760850382e359484304b0..edd4012c7756b753e30972537c64d7c25492463d 100644 GIT binary patch delta 2222 zcmb7FTWl0n7|xkqc6+7Q?Jh0l)Kbp2-AgZ8Na+G=Z&XTK0d1hv+3D=r?ag|S zx{jH(wph9xG%P7mbxKF%oToG&Pa7}nr?d@wbSAtC@T=hP2KeFHlE3t z)^g-Tf|f8bWPWQSgR^_n@Vqo!YTeytf?r#DyD}rA1c|bgOwRZP(U4RnIN6NILUoPhw4HK2be9a|R+=FtBbQx|qY2lS2(2H!@UJ<`p4< zZA3a!8$lafrAWq;ShlrT(^Sn?{@ma(9VJ!Gz?!X(OU8I=%%9Z!;y4y3Y-D3&r3b|% znc?_^8eMZGM58!rL#7+6Dp5?3WL{PHw3l?mF+tX`4f>{Wd+{@=#bhDQIymohks_bF z7)`0#L{wS}C4lJuVa`R4__mjz-oB#;UU`up9z1;T$WXYm8>i(kV#kixSVpU+&Bsi6`BUvvZc)pz+-v4yO2$lR**;D^*Budz3u^} z)?t4Y$fCCvKz4ON!vdo%tO>CZ5!FOelCkEu^F{Oq0^68{lRoL^@@u5HqG}ilvJA&k z28?XRNB)=ZqJZrtATmb5P>6mag33)f)*>4OEs&(cgrw_|68Awn8p-%?uLX3qpUbN> z#+g0)2<)87m5sWY)Y(q!?8Ri)t41um;W!%7rutP3Hy zHcBiPR?Uq`O7BNR@6~|ky7$2dJaIzlNNRX*hndvg9j7-tTDx<$BWNKyf+m_-@qG6{ z7tcq`t9I;+{EOuVn1t__twr0fVUuIb(99*kCK_9-W47Ivn0)1RL(q(M5gUtCLfH_m z2WMZLzePRh2--6EV{Z1is%9l z3W-&T(AM z8$GRW^|S)^<(7L|Gd_QlCa-1uL4RG&$m4ePE&uj)#@+aYN(ydrQMk4XZt-B?x$VDW7NAt(}kbUJvRooU81&dk^` z4L_-sSX3$!Z}B9kidgVjKmkQn7DxycsY^Z!0;;-b>8?^GB6U|)>V5X)Ll>i&^X|F# zo;mltVlTJPq{P_9VR*h|`Y(AJabq=UXtcNLeE@s+q@#>cPONp)P>?mSF#Yk6vu|)BBt2n=#Tgqg1Jp03C7B`y42< zbD+jqi_wH;sOF3f7+C8`#SzmYMln($wIeA0dlN>L_%4iw*o}nX-;kZGaw^HV%ujjZ zk_1quScNK=8tvP-DxS>y)skD>WNl|MzMOh^?u!nu*N;(F*NVk6l9F{a)7Vk&2gZf2 zK_)7KUl7^LpwLEt?HZknc0Unn-b~N?=7cR*8|XjZcT?%uNpSfl{VQDFfYI2Ak&(=p zJf8XFD3mGoeCAu_xP(zpPYN6C;cKCTjc)QZT#0Pix{4r|iDiQb;E89?f+M=(5Zi%D z!;x4gMviIfvY`~fC6`dsUL~~A?;?R9?P|jt?-(X*y8lf~l_bR8*CyE^E5Cvm027rpu-wyUpCXIYqbmJclD~ zS0c^y=ib0-Gvu7E>M@FnVb(7S8Cv|Yk^L|P%a0|ShhIqBR)yJ%4?T?+N=~uFm+p*4 zbXA1C%f|A=$-sTI8VbW&C|2I&Wj|e75e_htq^zlPPdPBaZ1+)`UWylpZ7X>qqv^qQT<)R4%`l}?ATDcKgv&Z; zfLKse8 { - const owner = "reviewdog"; - const repo = "reviewdog"; - const version = await tagToVersion(tag, owner, repo); +export async function installReviewdog( + tag: string, + directory: string +): Promise { + 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 { - 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(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; - } \ No newline at end of file +async function tagToVersion( + tag: string, + owner: string, + repo: string +): Promise { + 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(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 +} diff --git a/src/main.ts b/src/main.ts index 9deaf02..c8dd68f 100644 --- a/src/main.ts +++ b/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 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 { - 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 => { - 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 => { + 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

${content.message.rendered}
\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()