From 7f05c927832cec2ecabdbf6a59ea1bd94b26ec01 Mon Sep 17 00:00:00 2001 From: James Reeves Date: Thu, 25 May 2023 21:01:29 +0100 Subject: [PATCH] Add cljfmt support --- .github/workflows/smoke-tests.yml | 26 +++++ README.md | 5 + __tests__/cljfmt.test.ts | 153 ++++++++++++++++++++++++++++++ __tests__/entrypoint.test.ts | 13 +++ action.yml | 2 + src/cljfmt.ts | 75 +++++++++++++++ src/entrypoint.ts | 19 ++++ 7 files changed, 293 insertions(+) create mode 100644 __tests__/cljfmt.test.ts create mode 100644 src/cljfmt.ts diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 2454324..cb13f5e 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -191,6 +191,32 @@ jobs: - name: Check clj-kondo version run: clj-kondo --version + test-cljfmt: + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@main + + - name: Prepare java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '8' + + - name: Install cljfmt + uses: ./ + with: + cljfmt: latest + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check cljfmt version + run: cljfmt --version + test-cljstyle: strategy: matrix: diff --git a/README.md b/README.md index 913641a..e78110a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This action sets up Clojure tools environment for using in GitHub Actions. * [boot-clj](https://boot-clj.github.io/) * [babashka](https://babashka.org/) * [clj-kondo](https://github.com/clj-kondo/clj-kondo) +* [cljfmt](https://github.com/weavejester/cljfmt) * [cljstyle](https://github.com/greglook/cljstyle) * [deps.clj](https://github.com/borkdude/deps.clj) * [zprint](https://github.com/kkinnear/zprint) @@ -55,6 +56,7 @@ jobs: boot: 2.8.3 # Boot.clj bb: 0.7.8 # Babashka clj-kondo: 2022.05.31 # Clj-kondo + cljfmt: 0.10.2 # cljfmt cljstyle: 0.15.0 # cljstyle cmd-exe-workaround: 'latest' # Replaces `clojure` with `deps.clj` on Windows zprint: 1.2.3 # zprint @@ -89,6 +91,9 @@ jobs: - name: Get clj-kondo version run: clj-kondo --version + - name: Get cljfmt version + run: cljfmt --version + - name: Get cljstyle version # cljstyle is not yet available for windows if: ${{ matrix.os != 'windows-latest' }} diff --git a/__tests__/cljfmt.test.ts b/__tests__/cljfmt.test.ts new file mode 100644 index 0000000..0daf713 --- /dev/null +++ b/__tests__/cljfmt.test.ts @@ -0,0 +1,153 @@ +import _os from 'os' +import * as _core from '@actions/core' +import * as _tc from '@actions/tool-cache' +import * as cljfmt from '../src/cljfmt' + +const getJson = jest.fn() +jest.mock('@actions/http-client', () => ({ + HttpClient: function () { + return {getJson} + } +})) + +jest.mock('os') +const os: jest.Mocked = _os as never + +jest.mock('@actions/tool-cache') +const tc: jest.Mocked = _tc as never + +jest.mock('@actions/core') +const core: jest.Mocked = _core as never + +describe('cljfmt tests', () => { + beforeEach(() => { + jest.resetAllMocks() + }) + + describe('getLatestCljFmt', () => { + it('uses tag_name as latest version', async () => { + getJson.mockResolvedValueOnce({ + result: {tag_name: '1.2.3'} + }) + const res = await cljfmt.getLatestCljFmt() + expect(res).toBe('1.2.3') + expect(getJson).toHaveBeenCalledWith( + 'https://api.github.com/repos/weavejester/cljfmt/releases/latest', + undefined + ) + }) + + it('supports authorization', async () => { + getJson.mockResolvedValueOnce({ + result: {tag_name: '1.2.3'} + }) + const res = await cljfmt.getLatestCljFmt('token 123') + expect(res).toBe('1.2.3') + expect(getJson).toHaveBeenCalledWith( + 'https://api.github.com/repos/weavejester/cljfmt/releases/latest', + {Authorization: 'token 123'} + ) + }) + + it('throws on http client error', async () => { + getJson.mockRejectedValueOnce(new Error('some error')) + await expect(cljfmt.getLatestCljFmt()).rejects.toThrow('some error') + }) + + it('throws on wrong client answer', async () => { + getJson.mockResolvedValueOnce({result: {foo: 'bar'}}) + await expect(cljfmt.getLatestCljFmt()).rejects.toThrow( + `Can't obtain latest cljfmt version` + ) + }) + }) + + describe('getArtifactName', () => { + test.each` + platform | artifact + ${'win32'} | ${`cljfmt-1.2.3-win-amd64.zip`} + ${'darwin'} | ${`cljfmt-1.2.3-darwin-amd64.tar.gz`} + ${'linux'} | ${`cljfmt-1.2.3-linux-amd64.tar.gz`} + ${'foobar'} | ${`cljfmt-1.2.3-linux-amd64.tar.gz`} + `('$platform -> $artifact', ({platform, artifact}) => { + os.platform.mockReturnValueOnce(platform as never) + expect(cljfmt.getArtifactName('1.2.3')).toBe(artifact) + }) + }) + + describe('getArtifactUrl', () => { + test.each` + platform | artifact + ${'win32'} | ${`cljfmt-1.2.3-win-amd64.zip`} + ${'darwin'} | ${`cljfmt-1.2.3-darwin-amd64.tar.gz`} + ${'linux'} | ${`cljfmt-1.2.3-linux-amd64.tar.gz`} + ${'foobar'} | ${`cljfmt-1.2.3-linux-amd64.tar.gz`} + `('$platform -> $artifact', ({platform, artifact}) => { + os.platform.mockReturnValueOnce(platform as never) + expect(cljfmt.getArtifactUrl('1.2.3')).toBe( + `https://github.com/weavejester/cljfmt/releases/download/1.2.3/${artifact}` + ) + }) + }) + + describe('setup', () => { + it('uses cache', async () => { + tc.find.mockReturnValueOnce('/foo/bar') + + await cljfmt.setup('1.2.3') + + expect(tc.find).toHaveBeenCalledWith('cljfmt', '1.2.3') + expect(core.addPath).toHaveBeenCalledWith('/foo/bar') + }) + + it('uses cache', async () => { + tc.find.mockReturnValueOnce('/foo/bar') + + await cljfmt.setup('1.2.3') + + expect(tc.find).toHaveBeenCalledWith('cljfmt', '1.2.3') + expect(core.addPath).toHaveBeenCalledWith('/foo/bar') + }) + + it('fetches exact version', async () => { + tc.downloadTool.mockResolvedValueOnce('/foo/cljfmt.tar.gz') + tc.extractTar.mockResolvedValueOnce('/bar/baz') + tc.cacheDir.mockResolvedValueOnce('/qux') + + await cljfmt.setup('1.2.3', 'token 123') + + expect(tc.find).toHaveBeenCalledWith('cljfmt', '1.2.3') + expect(tc.downloadTool).toHaveBeenCalledWith( + 'https://github.com/weavejester/cljfmt/releases/download/1.2.3/cljfmt-1.2.3-linux-amd64.tar.gz', + undefined, + 'token 123' + ) + expect(tc.cacheDir).toHaveBeenCalledWith('/bar/baz', 'cljfmt', '1.2.3') + expect(core.addPath).toHaveBeenCalledWith('/qux') + }) + + it('fetches latest version', async () => { + getJson.mockResolvedValueOnce({ + result: {tag_name: '9.9.9'} + }) + tc.downloadTool.mockResolvedValueOnce('/foo/cljfmt.tar.gz') + tc.extractTar.mockResolvedValueOnce('/bar/baz') + tc.cacheDir.mockResolvedValueOnce('/qux') + + await cljfmt.setup('latest', 'token 123') + + expect(getJson).toHaveBeenCalledWith( + 'https://api.github.com/repos/weavejester/cljfmt/releases/latest', + {Authorization: 'token 123'} + ) + expect(tc.find).toHaveBeenCalledWith('cljfmt', '9.9.9') + expect(tc.downloadTool).toHaveBeenCalledWith( + 'https://github.com/weavejester/cljfmt/releases/download/9.9.9/cljfmt-9.9.9-linux-amd64.tar.gz', + undefined, + 'token 123' + ) + expect(tc.cacheDir).toHaveBeenCalledWith('/bar/baz', 'cljfmt', '9.9.9') + expect(core.addPath).toHaveBeenCalledWith('/qux') + }) + }) +}) diff --git a/__tests__/entrypoint.test.ts b/__tests__/entrypoint.test.ts index 97e68b9..4f71067 100644 --- a/__tests__/entrypoint.test.ts +++ b/__tests__/entrypoint.test.ts @@ -4,6 +4,7 @@ import * as _boot from '../src/boot' import * as _cli from '../src/cli' import * as _bb from '../src/babashka' import * as _cljKondo from '../src/clj-kondo' +import * as _cljfmt from '../src/cljfmt' import * as _cljstyle from '../src/cljstyle' import * as _zprint from '../src/zprint' import * as _utils from '../src/utils' @@ -27,6 +28,9 @@ const bb: jest.Mocked = _bb as never jest.mock('../src/clj-kondo') const cljKondo: jest.Mocked = _cljKondo as never +jest.mock('../src/cljfmt') +const cljfmt: jest.Mocked = _cljfmt as never + jest.mock('../src/cljstyle') const cljstyle: jest.Mocked = _cljstyle as never @@ -98,6 +102,15 @@ describe('setup-clojure', () => { expect(cljKondo.setup).toHaveBeenCalledWith('1.2.3', 'token abc') }) + it('sets up cljfmt', async () => { + inputs['cljfmt'] = '1.2.3' + inputs['github-token'] = 'abc' + + await main() + + expect(cljfmt.setup).toHaveBeenCalledWith('1.2.3', 'token abc') + }) + it('sets up cljstyle', async () => { inputs['cljstyle'] = '1.2.3' inputs['github-token'] = 'abc' diff --git a/action.yml b/action.yml index cf8223c..7da80bb 100644 --- a/action.yml +++ b/action.yml @@ -26,6 +26,8 @@ inputs: description: 'Babashka version to install, `latest` can be used.' clj-kondo: description: 'Clj-kondo version to install, `latest` can be used.' + cljfmt: + description: 'cljfmt version to install, `latest` can be used.' cljstyle: description: 'cljstyle version to install, `latest` can be used.' zprint: diff --git a/src/cljfmt.ts b/src/cljfmt.ts new file mode 100644 index 0000000..538a71d --- /dev/null +++ b/src/cljfmt.ts @@ -0,0 +1,75 @@ +import * as core from '@actions/core' +import * as http from '@actions/http-client' +import * as os from 'os' +import * as tc from '@actions/tool-cache' + +export const identifier = 'cljfmt' + +export async function getLatestCljFmt(githubAuth?: string): Promise { + const client = new http.HttpClient('actions/setup-cljfmt', undefined, { + allowRetries: true, + maxRetries: 3 + }) + + const res = await client.getJson<{tag_name: string}>( + `https://api.github.com/repos/weavejester/cljfmt/releases/latest`, + githubAuth ? {Authorization: githubAuth} : undefined + ) + + const result = res.result?.tag_name + if (result) { + return result + } + + throw new Error(`Can't obtain latest cljfmt version`) +} + +export function getArtifactName(version: string): string { + const platform = os.platform() + switch (platform) { + case 'win32': + return `cljfmt-${version}-win-amd64.zip` + case 'darwin': + return `cljfmt-${version}-darwin-amd64.tar.gz` + default: + return `cljfmt-${version}-linux-amd64.tar.gz` + } +} + +export function getArtifactUrl(version: string): string { + const archiveName = getArtifactName(version) + return `https://github.com/weavejester/cljfmt/releases/download/${version}/${archiveName}` +} + +export async function extract(source: string): Promise { + return source.endsWith('.zip') + ? await tc.extractZip(source) + : await tc.extractTar(source) +} + +export async function setup( + version: string, + githubAuth?: string +): Promise { + const ver = version === 'latest' ? await getLatestCljFmt(githubAuth) : version + + let toolDir = tc.find(identifier, ver) + if (!toolDir) { + const archiveUrl = getArtifactUrl(ver) + core.info(`Downloading: ${archiveUrl}`) + + const artifactFile = await tc.downloadTool( + archiveUrl, + undefined, + githubAuth + ) + + const extractedDir = await extract(artifactFile) + toolDir = await tc.cacheDir(extractedDir, identifier, ver) + core.info(`Caching directory: ${toolDir}`) + } else { + core.info(`Using cached directory: ${toolDir}`) + } + + core.addPath(toolDir) +} diff --git a/src/entrypoint.ts b/src/entrypoint.ts index 5b8295a..8cbfeb4 100644 --- a/src/entrypoint.ts +++ b/src/entrypoint.ts @@ -4,6 +4,7 @@ import * as boot from './boot' import * as cli from './cli' import * as bb from './babashka' import * as cljKondo from './clj-kondo' +import * as cljfmt from './cljfmt' import * as cljstyle from './cljstyle' import * as zprint from './zprint' import * as utils from './utils' @@ -18,6 +19,7 @@ export async function main(): Promise { CLI_VERSION, BB_VERSION, CLJ_KONDO_VERSION, + CLJFMT_VERSION, CLJSTYLE_VERSION, ZPRINT_VERSION } = getTools() @@ -52,6 +54,10 @@ export async function main(): Promise { tools.push(cljKondo.setup(CLJ_KONDO_VERSION, githubAuth)) } + if (CLJFMT_VERSION) { + tools.push(cljfmt.setup(CLJFMT_VERSION, githubAuth)) + } + if (CLJSTYLE_VERSION) { if (IS_WINDOWS) { throw new Error('cljstyle on windows is not supported yet.') @@ -84,6 +90,7 @@ export async function pre(): Promise { CLI_VERSION, BB_VERSION, CLJ_KONDO_VERSION, + CLJFMT_VERSION, CLJSTYLE_VERSION, ZPRINT_VERSION } = getTools() @@ -115,6 +122,10 @@ export async function pre(): Promise { tools.push(cache.restore(cljKondo.identifier, CLJ_KONDO_VERSION)) } + if (CLJFMT_VERSION) { + tools.push(cache.restore(cljfmt.identifier, CLJFMT_VERSION)) + } + if (CLJSTYLE_VERSION) { if (!IS_WINDOWS) { tools.push(cache.restore(cljstyle.identifier, CLJSTYLE_VERSION)) @@ -142,6 +153,7 @@ export async function post(): Promise { CLI_VERSION, BB_VERSION, CLJ_KONDO_VERSION, + CLJFMT_VERSION, CLJSTYLE_VERSION, ZPRINT_VERSION } = getTools() @@ -173,6 +185,10 @@ export async function post(): Promise { tools.push(cache.save(cljKondo.identifier, CLJ_KONDO_VERSION)) } + if (CLJFMT_VERSION) { + tools.push(cache.save(cljfmt.identifier, CLJFMT_VERSION)) + } + if (CLJSTYLE_VERSION) { if (!IS_WINDOWS) { tools.push(cache.save(cljstyle.identifier, CLJSTYLE_VERSION)) @@ -197,6 +213,7 @@ export type Tools = { CLI_VERSION: string | null | undefined BB_VERSION: string | null | undefined CLJ_KONDO_VERSION: string | null | undefined + CLJFMT_VERSION: string | null | undefined CLJSTYLE_VERSION: string | null | undefined ZPRINT_VERSION: string | null | undefined DEPS_EXE_VERSION: string | null | undefined @@ -209,6 +226,7 @@ function getTools(): Tools { const CLI_VERSION = core.getInput('cli') const BB_VERSION = core.getInput('bb') const CLJ_KONDO_VERSION = core.getInput('clj-kondo') + const CLJFMT_VERSION = core.getInput('cljfmt') const CLJSTYLE_VERSION = core.getInput('cljstyle') const ZPRINT_VERSION = core.getInput('zprint') const DEPS_EXE_VERSION = core.getInput('deps.exe') @@ -220,6 +238,7 @@ function getTools(): Tools { CLI_VERSION, BB_VERSION, CLJ_KONDO_VERSION, + CLJFMT_VERSION, CLJSTYLE_VERSION, ZPRINT_VERSION, DEPS_EXE_VERSION