From 6f8edca9848902b1be24c74b90e53768cf2104fd Mon Sep 17 00:00:00 2001 From: realmarv Date: Tue, 4 Apr 2023 13:39:57 +0330 Subject: [PATCH] scripts/styleChecker.fsx: add Add styleChecker.fsx script to check style of our F#, TS and YML codes. The `git respore package.json` command in the styleChecker.fsx script was failing with the following error, even when I was running the `git config --global --add safe.directory '*'` command in both the styleChecker.fsx script and in the CI. In the issue [1], it's suggested by someone to use --system instead of --global in the mentioned command, when the git command is running in a container, which solved the problem. ``` fatal: detected dubious ownership in repository at '/__w/conventions/conventions' To add an exception for this directory, call: git config --global --add safe.directory /__w/conventions/conventions Error when running 'git restore package.json' Fsdk.Process+ProcessFailed: Exception of type 'Fsdk.Process+ProcessFailed' was thrown. at Fsdk.Process.ProcessResult.Unwrap(String errMsg) at Fsdk.Process.ProcessResult.UnwrapDefault() at FSI_0002.RunPrettier(String arguments) at .$FSI_0002.main@() Stopped due to error Error: Process completed with exit code 1. ``` [1] https://github.com/actions/checkout/issues/1048 --- .github/workflows/CI.yml | 4 +- scripts/styleChecker.fsx | 304 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 1 deletion(-) create mode 100755 scripts/styleChecker.fsx diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e389d9f8..e4cdd3f8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -139,7 +139,7 @@ jobs: fetch-depth: 0 # workaround for https://github.com/actions/runner/issues/2033 - name: ownership workaround - run: git config --global --add safe.directory '*' + run: git config --system --add safe.directory '*' - name: Print versions run: | git --version @@ -213,3 +213,5 @@ jobs: dotnet tool install fantomless-tool --version 4.7.997-prerelease dotnet fantomless --recurse . git diff --exit-code + - name: Check style of our F#, TypeScript and YML code + run: sudo dotnet fsi scripts/styleChecker.fsx diff --git a/scripts/styleChecker.fsx b/scripts/styleChecker.fsx new file mode 100755 index 00000000..2378c493 --- /dev/null +++ b/scripts/styleChecker.fsx @@ -0,0 +1,304 @@ +#!/usr/bin/env -S dotnet fsi + +#r "nuget: Mono.Unix, Version=7.1.0-final.1.21458.1" +#r "nuget: Fsdk, Version=0.6.0--date20230214-0422.git-1ea6f62" +#load "../src/FileConventions/Helpers.fs" + +open System +open System.IO + +open Fsdk +open Fsdk.Process + +open Helpers + +let fantomlessToolVersion = "4.7.997-prerelease" +let prettierVersion = "2.8.3" + +let InstallFantomlessTool(version: string) = + let isFantomlessInstalled = + let installedPackages: string = + Process + .Execute( + { + Command = "dotnet" + Arguments = "tool list" + }, + Echo.Off + ) + .UnwrapDefault() + + installedPackages.Split Environment.NewLine + |> Seq.map(fun line -> + line.Contains "fantomless-tool" + && line.Contains fantomlessToolVersion + ) + |> Seq.contains true + + if not(isFantomlessInstalled) then + Process + .Execute( + { + Command = "dotnet" + Arguments = "new tool-manifest --force" + }, + Echo.Off + ) + .UnwrapDefault() + |> ignore + + Process + .Execute( + { + Command = "dotnet" + Arguments = + $"tool install fantomless-tool --version {version}" + }, + Echo.Off + ) + .UnwrapDefault() + |> ignore + + Process + .Execute( + { + Command = "dotnet" + Arguments = "tool restore" + }, + Echo.Off + ) + .UnwrapDefault() + |> ignore + +let UnwrapProcessResult + (suggestion: string) + (raiseError: bool) + (processResult: ProcessResult) + : string = + let errMsg = + sprintf + "Error when running '%s %s'" + processResult.Details.Command + processResult.Details.Args + + match processResult.Result with + | Success output -> + Console.WriteLine output + output + | Error(_, output) -> + if processResult.Details.Echo = Echo.Off then + output.PrintToConsole() + Console.WriteLine() + Console.Out.Flush() + + let fullErrMsg = errMsg + Environment.NewLine + suggestion + Console.Error.WriteLine fullErrMsg + + if raiseError then + raise <| ProcessFailed errMsg + else + fullErrMsg + | WarningsOrAmbiguous output -> + if processResult.Details.Echo = Echo.Off then + output.PrintToConsole() + Console.WriteLine() + Console.Out.Flush() + + let fullErrMsg = sprintf "%s (with warnings?)" errMsg + Console.Error.WriteLine fullErrMsg + fullErrMsg + +let IsProcessSuccessful(processResult: ProcessResult) : bool = + match processResult.Result with + | Success output -> true + | _ -> false + +let InstallPrettier(version: string) = + let isPrettierInstalled = + Process.Execute( + { + Command = "npm" + Arguments = $"list prettier@{version}" + }, + Echo.All + ) + |> IsProcessSuccessful + + if not(isPrettierInstalled) then + Process.Execute( + { + Command = "npm" + Arguments = $"install prettier@{version}" + }, + Echo.Off + ) + |> UnwrapProcessResult "" true + |> ignore + +let StyleFSharpFiles(rootDir: DirectoryInfo) = + InstallFantomlessTool(fantomlessToolVersion) + + Process + .Execute( + { + Command = "dotnet" + Arguments = $"fantomless --recurse {rootDir.FullName}" + }, + Echo.Off + ) + .UnwrapDefault() + |> ignore + +let RunPrettier(arguments: string) = + + // We need this step so we can change the files using `npx prettier --write` in the next step. + // Otherwise we get permission denied error in the CI. + Process + .Execute( + { + Command = "chmod" + Arguments = "777 --recursive ." + }, + Echo.Off + ) + .UnwrapDefault() + |> ignore + + Process.Execute( + { + Command = "npx" + Arguments = $"prettier {arguments}" + }, + Echo.Off + ) + |> UnwrapProcessResult "" true + |> ignore + + + // Since after installing commitlint dependencies package.json file changes, we need to + // run the following command to ignore package.json file + Process + .Execute( + { + Command = "git" + Arguments = "restore package.json" + }, + Echo.Off + ) + .UnwrapDefault() + |> ignore + +let StyleTypeScriptFiles() = + RunPrettier "--quote-props=consistent --write ./**/*.ts" + +let StyleYmlFiles() = + RunPrettier "--quote-props=consistent --write ./**/*.yml" + +let ContainsFiles (rootDir: DirectoryInfo) (searchPattern: string) = + Helpers.GetFiles rootDir searchPattern |> Seq.length > 0 + +let GitDiff() : ProcessResult = + + // Since we changed file modes in the prettier step we need the following command to + // make git ignore mode changes in files and doesn't include them in the git diff command. + Process + .Execute( + { + Command = "git" + Arguments = "config core.fileMode false" + }, + Echo.Off + ) + .UnwrapDefault() + |> ignore + + let processResult = + Process.Execute( + { + Command = "git" + Arguments = "diff --exit-code" + }, + Echo.Off + ) + + processResult + +let GitRestore() = + Process + .Execute( + { + Command = "git" + Arguments = "restore ." + }, + Echo.Off + ) + .UnwrapDefault() + |> ignore + +let CheckStyleOfFSharpFiles(rootDir: DirectoryInfo) : bool = + let suggestion = + "Please style your F# code using: `dotnet fantomless --recurse .`" + + GitRestore() + + let success = + if ContainsFiles rootDir "*.fs" || ContainsFiles rootDir ".fsx" then + StyleFSharpFiles rootDir + let processResult = GitDiff() + UnwrapProcessResult suggestion false processResult |> ignore + IsProcessSuccessful processResult + + else + true + + success + +let CheckStyleOfTypeScriptFiles(rootDir: DirectoryInfo) : bool = + let suggestion = + "Please style your TypeScript code using: `npx prettier --quote-props=consistent --write ./**/*.ts`" + + GitRestore() + + let success = + if ContainsFiles rootDir "*.ts" then + InstallPrettier prettierVersion + StyleTypeScriptFiles() + let processResult = GitDiff() + UnwrapProcessResult suggestion false processResult |> ignore + IsProcessSuccessful processResult + + else + true + + success + +let CheckStyleOfYmlFiles(rootDir: DirectoryInfo) : bool = + let suggestion = + "Please style your YML code using: `npx prettier --quote-props=consistent --write ./**/*.yml`" + + GitRestore() + + let success = + if ContainsFiles rootDir "*.yml" then + InstallPrettier prettierVersion + StyleYmlFiles() + let processResult = GitDiff() + UnwrapProcessResult suggestion false processResult |> ignore + IsProcessSuccessful processResult + else + true + + success + + +let rootDir = Path.Combine(__SOURCE_DIRECTORY__, "..") |> DirectoryInfo + +let processSuccessStates = + [| + CheckStyleOfFSharpFiles rootDir + CheckStyleOfTypeScriptFiles rootDir + CheckStyleOfYmlFiles rootDir + |] + +if processSuccessStates |> Seq.contains false then + Environment.Exit 1