diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5c23e7db2..1c830059f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -90,7 +90,7 @@ jobs: npm --version - name: Install yarn run: | - npm install --verbose --global yarn + npm install --verbose --global yarn@1.22.19 yarn add --dev typescript - name: Install commitlint dependencies run: | @@ -121,7 +121,7 @@ jobs: sudo apt install --yes --no-install-recommends npm curl - name: Install yarn run: | - npm install --verbose --global yarn + npm install --verbose --global yarn@1.22.19 yarn add --dev typescript ts-node - name: Install commitlint dependencies run: | @@ -210,6 +210,8 @@ jobs: run: dotnet fsi scripts/unpinnedNugetPackageReferenceVersionsInFSharpScripts.fsx - name: Check there are no unpinned versions in `dotnet tool install` commands run: dotnet fsi scripts/unpinnedDotnetToolInstallVersions.fsx + - name: Check there are no unpinned versions in `npm install` commands + run: dotnet fsi scripts/unpinnedNpmPackageInstallVersions.fsx - name: Check commits 1 by 1 if: github.event_name == 'pull_request' run: dotnet fsi scripts/checkCommits1by1.fsx diff --git a/scripts/unpinnedNpmPackageInstallVersions.fsx b/scripts/unpinnedNpmPackageInstallVersions.fsx new file mode 100755 index 000000000..c93620cce --- /dev/null +++ b/scripts/unpinnedNpmPackageInstallVersions.fsx @@ -0,0 +1,23 @@ +#!/usr/bin/env -S dotnet fsi + +open System +open System.IO + +#r "nuget: Mono.Unix, Version=7.1.0-final.1.21458.1" +#r "nuget: YamlDotNet, Version=16.1.3" + +#load "../src/FileConventions/Library.fs" +#load "../src/FileConventions/Helpers.fs" + +let rootDir = Path.Combine(__SOURCE_DIRECTORY__, "..") |> DirectoryInfo + +let invalidFiles = + Helpers.GetInvalidFiles + rootDir + "*.yml" + FileConventions.DetectUnpinnedNpmPackageInstallVersions + +let message = + "Please define the package version number in the `npm install` commands." + +Helpers.AssertNoInvalidFiles invalidFiles message diff --git a/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion1.yml b/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion1.yml new file mode 100644 index 000000000..4f0e52c33 --- /dev/null +++ b/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion1.yml @@ -0,0 +1,11 @@ +name: CI + +on: [push, pull_request] + +jobs: + file-conventions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install prettier without specifying its version + run: npm install prettier diff --git a/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion2.yml b/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion2.yml new file mode 100644 index 000000000..a05bab538 --- /dev/null +++ b/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion2.yml @@ -0,0 +1,11 @@ +name: CI + +on: [push, pull_request] + +jobs: + file-conventions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install an npm package without specifying its version + run: sudo npm install --save-dev @prettier/plugin-xml diff --git a/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion3.yml b/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion3.yml new file mode 100644 index 000000000..87ed0e981 --- /dev/null +++ b/src/FileConventions.Test/DummyFiles/DummyCIWithUnpinnedNpmPackageInstallVersion3.yml @@ -0,0 +1,11 @@ +name: CI + +on: [push, pull_request] + +jobs: + file-conventions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install two npm packages, one with version and the other one without version + run: sudo npm install --save-dev @prettier/plugin-xml prettier@2.4.0 diff --git a/src/FileConventions.Test/DummyFiles/DummyCIWithoutUnpinnedDotnetToolInstallVersion.yml b/src/FileConventions.Test/DummyFiles/DummyCIWithoutUnpinnedDotnetToolInstallVersion.yml new file mode 100644 index 000000000..10084073b --- /dev/null +++ b/src/FileConventions.Test/DummyFiles/DummyCIWithoutUnpinnedDotnetToolInstallVersion.yml @@ -0,0 +1,16 @@ +name: CI + +on: [push, pull_request] + +jobs: + build: + name: Build + runs-on: ubuntu-22.04 + container: + image: "ubuntu:22.04" + steps: + - name: Install fantomless-tool + run: | + dotnet tool install fantomless-tool --version 4.8.999 + - name: Print "Hello World!" + run: echo "Hello World" diff --git a/src/FileConventions.Test/DummyFiles/DummyCIWithoutUnpinnedNpmPackageInstallVersion.yml b/src/FileConventions.Test/DummyFiles/DummyCIWithoutUnpinnedNpmPackageInstallVersion.yml new file mode 100644 index 000000000..080f74605 --- /dev/null +++ b/src/FileConventions.Test/DummyFiles/DummyCIWithoutUnpinnedNpmPackageInstallVersion.yml @@ -0,0 +1,11 @@ +name: CI + +on: [push, pull_request] + +jobs: + file-conventions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install prettier with specifying its version + run: npm install prettier@2.8.3 diff --git a/src/FileConventions.Test/FileConventions.Test.fs b/src/FileConventions.Test/FileConventions.Test.fs index 81d82bc68..736d41807 100644 --- a/src/FileConventions.Test/FileConventions.Test.fs +++ b/src/FileConventions.Test/FileConventions.Test.fs @@ -140,6 +140,85 @@ let DetectUnpinnedDotnetToolInstallVersions1() = Is.EqualTo true ) +[] +let DetectUnpinnedDotnetToolInstallVersions2() = + let fileInfo = + (FileInfo( + Path.Combine( + dummyFilesDirectory.FullName, + "DummyCIWithoutUnpinnedDotnetToolInstallVersion.yml" + ) + )) + + Assert.That( + DetectUnpinnedDotnetToolInstallVersions fileInfo, + Is.EqualTo false + ) + + +[] +let DetectUnpinnedNpmPackageInstallVersions1() = + let fileInfo = + (FileInfo( + Path.Combine( + dummyFilesDirectory.FullName, + "DummyCIWithUnpinnedNpmPackageInstallVersion1.yml" + ) + )) + + Assert.That( + DetectUnpinnedNpmPackageInstallVersions fileInfo, + Is.EqualTo true + ) + + +[] +let DetectUnpinnedNpmPackageInstallVersions2() = + let fileInfo = + (FileInfo( + Path.Combine( + dummyFilesDirectory.FullName, + "DummyCIWithoutUnpinnedNpmPackageInstallVersion.yml" + ) + )) + + Assert.That( + DetectUnpinnedNpmPackageInstallVersions fileInfo, + Is.EqualTo false + ) + + +[] +let DetectUnpinnedNpmPackageInstallVersions3() = + let fileInfo = + (FileInfo( + Path.Combine( + dummyFilesDirectory.FullName, + "DummyCIWithUnpinnedNpmPackageInstallVersion2.yml" + ) + )) + + Assert.That( + DetectUnpinnedNpmPackageInstallVersions fileInfo, + Is.EqualTo true + ) + + +[] +let DetectUnpinnedNpmPackageInstallVersions4() = + let fileInfo = + (FileInfo( + Path.Combine( + dummyFilesDirectory.FullName, + "DummyCIWithUnpinnedNpmPackageInstallVersion3.yml" + ) + )) + + Assert.That( + DetectUnpinnedNpmPackageInstallVersions fileInfo, + Is.EqualTo true + ) + [] let DetectAsteriskInPackageReferenceItems1() = diff --git a/src/FileConventions/Library.fs b/src/FileConventions/Library.fs index e471f54cd..a8ff42703 100644 --- a/src/FileConventions/Library.fs +++ b/src/FileConventions/Library.fs @@ -73,7 +73,7 @@ let DetectUnpinnedVersionsInGitHubCI(fileInfo: FileInfo) = latestTagInRunsOnRegex.IsMatch fileText let DetectUnpinnedDotnetToolInstallVersions(fileInfo: FileInfo) = - assert (fileInfo.FullName.EndsWith(".yml")) + assert fileInfo.FullName.EndsWith ".yml" let fileLines = File.ReadLines fileInfo.FullName @@ -84,12 +84,46 @@ let DetectUnpinnedDotnetToolInstallVersions(fileInfo: FileInfo) = fileLines |> Seq.filter dotnetToolInstallRegex.IsMatch |> Seq.filter(fun line -> - not(line.Contains("--version")) && not(line.Contains("-v")) + not(line.Contains "--version") && not(line.Contains "-v") ) |> (fun unpinnedVersions -> Seq.length unpinnedVersions > 0) unpinnedDotnetToolInstallVersions +let DetectUnpinnedNpmPackageInstallVersions(fileInfo: FileInfo) = + assert fileInfo.FullName.EndsWith ".yml" + + let fileLines = File.ReadLines fileInfo.FullName + + let npmPackageInstallRegex = + Regex("npm\\s+install\\s+", RegexOptions.Compiled) + + let npmPackageVersionRegex = + Regex("@((\\d+\\.\\d+\\.\\d+)|(\\$[A-Z_]+))", RegexOptions.Compiled) + + let unpinnedNpmPackageInstallVersions = + fileLines + |> Seq.filter(fun line -> npmPackageInstallRegex.IsMatch line) + |> Seq.filter(fun line -> + let npmPackagesRegex = + Regex("(?<=npm install ).*$", RegexOptions.Compiled) + + let npmInstallPackages = npmPackagesRegex.Match line + + let numNpmInstallPackages = + npmInstallPackages.Value.Split(" ") + |> Seq.filter(fun word -> word.Trim().StartsWith("-") |> not) + |> Seq.length + + let numNpmInstallVersions = + npmPackageVersionRegex.Matches line |> Seq.length + + numNpmInstallPackages = numNpmInstallVersions |> not + ) + |> (fun unpinnedVersions -> Seq.length unpinnedVersions > 0) + + unpinnedNpmPackageInstallVersions + let DetectAsteriskInPackageReferenceItems(fileInfo: FileInfo) = assert (fileInfo.FullName.EndsWith "proj")