diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d49047e9..7682f8ba 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -77,7 +77,7 @@ jobs: sudo apt install --yes --no-install-recommends npm curl # need to update nodejs because with ubuntu's default nodejs version we would get this error: # error @jest/core@29.4.1: The engine "node" is incompatible with this module. Expected version "^14.15.0 || ^16.10.0 || >=18.0.0". Got "12.22.9" - sudo npm install --global n + sudo npm install --global n@9.1.0 sudo n lts - name: Print versions run: | @@ -86,12 +86,12 @@ jobs: npm --version - name: Install yarn run: | - npm install --global yarn + npm install --global yarn@1.22.19 yarn add --dev jest typescript ts-jest @types/jest - name: Install commitlint run: | - npm install conventional-changelog-conventionalcommits - npm install commitlint@latest + npm install conventional-changelog-conventionalcommits@6.1.0 + npm install commitlint@17.6.6 - name: Print versions run: | git --version @@ -130,7 +130,7 @@ jobs: # at wrapSafe (internal/modules/cjs/loader.js:915:16) # ... # ``` - sudo npm install --global n + sudo npm install --global n@9.1.0 sudo n lts - uses: actions/checkout@v2 with: @@ -179,6 +179,8 @@ jobs: run: dotnet fsi scripts/unpinnedNugetPackageReferenceVersions.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 100644 index 00000000..d1969334 --- /dev/null +++ b/scripts/unpinnedNpmPackageInstallVersions.fsx @@ -0,0 +1,20 @@ +#!/usr/bin/env -S dotnet fsi + +open System +open System.IO + +#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 00000000..4f0e52c3 --- /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 00000000..a05bab53 --- /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 00000000..87ed0e98 --- /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 00000000..10084073 --- /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 00000000..080f7460 --- /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 5e756b3a..fa0811b9 100644 --- a/src/FileConventions.Test/FileConventions.Test.fs +++ b/src/FileConventions.Test/FileConventions.Test.fs @@ -138,6 +138,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 9fc1f1e9..76f05040 100644 --- a/src/FileConventions/Library.fs +++ b/src/FileConventions/Library.fs @@ -49,7 +49,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 @@ -60,12 +60,46 @@ let DetectUnpinnedDotnetToolInstallVersions(fileInfo: FileInfo) = fileLines |> Seq.filter(fun line -> dotnetToolInstallRegex.IsMatch line) |> 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+", 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")