diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 913faf0..ab2b30b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,8 +42,11 @@ jobs: gleam-version: ${{ env.gleam }} rebar3-version: ${{ env.rebar }} - run: gleam test --target erlang + - run: gleam build --target erlang + - run: gleam run -m go_over -- --format sarif --sarif-output go-over.sarif --local + - run: python3 -c "import json; d=json.load(open('go-over.sarif')); assert d['version']=='2.1.0' and d['runs']" - run: gleam run --target erlang -- --local --puller wget - - run: gleam run --target erlang -- --local --outdated + - run: gleam run --target erlang -- --local windows-erlang: #todo swap to 25 when setup-beam is fixed @@ -59,7 +62,7 @@ jobs: otp-version: ${{ env.otp }} gleam-version: ${{ env.gleam }} rebar3-version: ${{ env.rebar }} - - run: gleam run --target erlang -- --local --outdated + - run: gleam run --target erlang -- --local windows-node: #todo swap to 25 when setup-beam is fixed @@ -80,7 +83,7 @@ jobs: node-version: ${{ env.nodelts }} cache: "npm" - run: npm install - - run: gleam run --target javascript --runtime nodejs -- --local --outdated --puller curl + - run: gleam run --target javascript --runtime nodejs -- --local --puller curl node: runs-on: ubuntu-latest @@ -106,7 +109,7 @@ jobs: cache: "npm" - run: npm install - run: gleam test --target javascript --runtime nodejs - - run: gleam run --target javascript --runtime nodejs -- --local --outdated --puller ${{ matrix.puller }} + - run: gleam run --target javascript --runtime nodejs -- --local --puller ${{ matrix.puller }} bun: runs-on: ubuntu-latest @@ -128,7 +131,7 @@ jobs: - run: bun install - run: gleam test --target javascript --runtime bun - run: gleam run --target javascript --runtime bun -- --local --puller wget - - run: gleam run --target javascript --runtime bun -- --local --outdated + - run: gleam run --target javascript --runtime bun -- --local deno: runs-on: ubuntu-latest @@ -150,4 +153,4 @@ jobs: - run: deno install - run: gleam test --target javascript --runtime deno - run: gleam run --target javascript --runtime deno -- --local --puller wget - - run: gleam run --target javascript --runtime deno -- --local --outdated + - run: gleam run --target javascript --runtime deno -- --local diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml index 871cb31..139ca38 100644 --- a/.github/workflows/deps.yml +++ b/.github/workflows/deps.yml @@ -31,7 +31,11 @@ jobs: gleam-version: ${{ env.gleam }} rebar3-version: ${{ env.rebar }} - run: gleam build - - run: gleam run -m go_over -- --outdated --local + - run: gleam run -m go_over -- --format sarif --sarif-output go-over.sarif --local + - uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: go-over.sarif + - run: gleam run -m go_over -- --outdated # create an issue in the repo if there are # outdated or vulnerable dependencies - uses: jayqi/failed-build-issue-action@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1bc7e46 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +## 4.0.0 + +### Added + +- `--format sarif` and `--sarif-output PATH` for GitHub Code Scanning + integration +- `--workspace [PATH]` to audit every Gleam project under a directory +- `--root PATH` to audit a single project outside the current directory +- `workspace_max_depth` config option (default: `3`) for workspace discovery +- Info-level warnings for unnecessary ignore rules, git dependencies, and + workspace projects skipped due to depth limits +- `--local` and `--global` flags (previously config-only) + +### Changed + +- Built-in outdated dependency checks removed from hex.pm metadata; `--outdated` + and `[go-over] outdated` now run `gleam deps outdated` instead (default: + `false`) +- `cache` config replaced by `force` (`cache = false` in v3 meant always + refresh; use `force = true` in v4) +- `--ignore-indirect` CLI flag removed; use `[go-over.ignore] indirect = true` +- CLI `--format` overrides per-project `[go-over] format` in workspace mode +- Advisories repository is cloned once per audit instead of per check +- Default `global` cache behavior unchanged (`true`) diff --git a/README.md b/README.md index 21e9949..eaaa865 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,10 @@ [![gleam js](https://img.shields.io/badge/%20gleam%20%E2%9C%A8-js%20%F0%9F%8C%B8-yellow)](https://gleam.run/news/v0.16-gleam-compiles-to-javascript/) [![gleam erlang](https://img.shields.io/badge/erlang%20%E2%98%8E%EF%B8%8F-red?style=flat&label=gleam%20%E2%9C%A8)](https://gleam.run) -![logo](images/go-over-logo.png) - A tool to audit Erlang & Elixir dependencies, to make sure your ✨ gleam projects really sparkle! -âš ī¸ Dependencies sourced directly from git or locally have limited support, only -checking for security advisories and not retirements or outdated versions +![logo](https://raw.githubusercontent.com/bwireman/go-over/main/images/go-over-logo.png) # đŸ”Ŋ Install @@ -52,14 +49,25 @@ gleam run -m go_over ### 🏴 Flags -- `--format` Specify the output format of any warnings, [minimal, verbose, json] - (default: None) +- `--format` Specify the output format of any warnings, [minimal, detailed, + json, sarif] (default: None) +- `--sarif-output PATH` Write SARIF output to `PATH` instead of stdout (requires + `--format sarif`) - `--puller` Specify the tool used to reach out to hex.pm, [native, curl, wget, httpie] (default: None) - `--force`: Force pulling new data even if the cached data is still valid -- `--outdated`: Additionally check if newer versions of dependencies exist -- `--ignore-indirect`: Ignore all warnings for indirect dependencies +- `--outdated`: **[deprecated]** runs `gleam deps outdated` instead — use that + command directly - `--verbose`: Print progress as packages are checked +- `--root PATH`: Audit a single Gleam project at `PATH` (uses `PATH/gleam.toml` + and `PATH/manifest.toml`) +- `--workspace [PATH]`: Audit every Gleam project under `PATH` (default: `.`). + Finds directories containing both `gleam.toml` and `manifest.toml`. Each + project's own `[go-over]` settings apply during its audit. Set + `workspace_max_depth` in the scan root's `gleam.toml` to control discovery + depth (default: `3`). +- `--local`: Cache data in the project's `.go-over/` directory +- `--global`: Cache data in the user's home directory (shared across projects) - `--help,-h`: Print help Flags override config values if set @@ -70,19 +78,22 @@ Optional settings that can be added to your project's `gleam.toml` ```toml [go-over] -# disables caching if false -# default: true -cache = true +# force pulling new data even if cached data is still valid +# default: false +force = false +# maximum directory depth when scanning with --workspace (set on the scan root) +# default: 3 +workspace_max_depth = 3 # if true all cached data will be stored in user's home directory # allowing cache to be shared between projects # default: true global = true -# sets output format for warnings ["minimal", "detailed", "json"] +# sets output format for warnings ["minimal", "detailed", "json", "sarif"] # default: "minimal" format = "minimal" -# will additionally check if newer versions of dependencies exist -# default: true -outdated = true +# [deprecated] runs `gleam deps outdated` — use that command directly instead +# default: false +outdated = false # tool used to pull information from hex.pm ["native", "curl", "wget", "httpie"] # default: "curl" for JS and "native" for Erlang puller = "curl" @@ -129,11 +140,45 @@ actions = [ ## âš™ī¸ CI -You can also schedule daily runs to keep your deps up to date and open issues -when necessary! +You can schedule daily runs to keep your deps up to date and open issues when +necessary! [Example â–ļī¸](https://github.com/bwireman/go-over/blob/main/.github/workflows/deps.yml) -# đŸ–Œī¸ Other Art +```yaml +- run: gleam run -m go_over -- --local +``` + +### SARIF output (GitHub Code Scanning) + +Use `--format sarif` to emit a +[SARIF 2.1.0](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning) +log suitable for GitHub's code scanning upload action: + +```yaml +- run: gleam build +- run: gleam run -m go_over -- --format sarif --sarif-output go-over.sarif +- uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: go-over.sarif +``` + +By default SARIF is written to stdout. Use `--sarif-output` to write directly to +a file instead of shell redirection. Run `gleam build` first so compile output +does not mix into stdout. Info-level notices (unnecessary ignores, skipped +workspace projects, git dependencies) are included as SARIF `note` results. + +You can validate SARIF output against GitHub ingestion rules at +https://sarifweb.azurewebsites.net/Validation. + +In workspace mode (`--workspace`), each Gleam project appears as a separate run +in the SARIF document. Pass `--format` on the CLI to use one format for every +project; otherwise each project's `[go-over] format` must match. + +## Upgrading to v4 + +See [CHANGELOG.md](CHANGELOG.md) for breaking changes from v3. + +# Other Art - As I'm sure is no surprise this tool is inspired by (and all around worse than) [mirego/mix_audit](https://github.com/mirego/mix_audit). Please check it @@ -141,15 +186,15 @@ when necessary! - It also draws inspiration from [mix hex.audit](https://hexdocs.pm/hex/Mix.Tasks.Hex.Audit.html) -# âš–ī¸ License +# License -- This tool uses - [mirego/elixir-security-advisories](https://github.com/mirego/elixir-security-advisories) - which is it self licensed with +This tool uses +[mirego/elixir-security-advisories](https://github.com/mirego/elixir-security-advisories) +which is it self licensed with - - `BSD-3-Clause license` - - `CC-BY 4.0 open source license`. +- `BSD-3-Clause` +- `CC-BY 4.0 open source` - See their [#license section](https://github.com/mirego/elixir-security-advisories?tab=readme-ov-file#license) -- Code original to this repo is Licensed under `MIT` +Code original to this repo is Licensed under `MIT` diff --git a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_empty_licenses_json.accepted b/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_empty_licenses_json.accepted deleted file mode 100644 index 49dfaa6..0000000 --- a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_empty_licenses_json.accepted +++ /dev/null @@ -1,14 +0,0 @@ ---- -version: 1.2.6 -title: Erlang@decode_latest_stable_version_and_licenses@test/testdata/hex/empty_licenses.json ---- -#( - "{ - "meta": { - "licenses": [] - }, - "latest_stable_version": "2.1.0" -} -", - Ok(HexInfo(Some("2.1.0"), [])), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_full_json.accepted b/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_full_json.accepted deleted file mode 100644 index a3462e5..0000000 --- a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_full_json.accepted +++ /dev/null @@ -1,14 +0,0 @@ ---- -version: 1.2.6 -title: Erlang@decode_latest_stable_version_and_licenses@test/testdata/hex/full.json ---- -#( - "{ - "meta": { - "licenses": ["MIT"] - }, - "latest_stable_version": "2.1.0" -} -", - Ok(HexInfo(Some("2.1.0"), ["MIT"])), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_multi_license_json.accepted b/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_multi_license_json.accepted deleted file mode 100644 index a3299ca..0000000 --- a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_multi_license_json.accepted +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: 1.2.6 -title: Erlang@decode_latest_stable_version_and_licenses@test/testdata/hex/multi_license.json ---- -#( - "{ - "meta": { - "licenses": ["foo", "BAR", "baz"] - }, - "latest_stable_version": "2.1.0" -} -", - Ok(HexInfo( - Some("2.1.0"), - ["foo", "BAR", "baz"], - )), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_license_json.accepted b/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_license_json.accepted deleted file mode 100644 index 6a5a36e..0000000 --- a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_license_json.accepted +++ /dev/null @@ -1,19 +0,0 @@ ---- -version: 1.3.0 -title: Erlang@decode_latest_stable_version_and_licenses@test/testdata/hex/no_license.json ---- -#( - "{ - "meta": { - }, - "latest_stable_version": "2.1.0" -} -", - Error(UnableToDecode([ - DecodeError( - "Field", - "Nothing", - ["meta", "licenses"], - ), - ])), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_meta_json.accepted b/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_meta_json.accepted deleted file mode 100644 index 77de6b6..0000000 --- a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_meta_json.accepted +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: 1.3.0 -title: Erlang@decode_latest_stable_version_and_licenses@test/testdata/hex/no_meta.json ---- -#( - "{ - "latest_stable_version": "2.1.0" -} -", - Error(UnableToDecode([ - DecodeError( - "Field", - "Nothing", - ["meta"], - ), - ])), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_version_json.accepted b/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_version_json.accepted deleted file mode 100644 index 8dcf848..0000000 --- a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_no_version_json.accepted +++ /dev/null @@ -1,19 +0,0 @@ ---- -version: 1.3.0 -title: Erlang@decode_latest_stable_version_and_licenses@test/testdata/hex/no_version.json ---- -#( - "{ - "meta": { - "licenses": ["bin"] - } -} -", - Error(UnableToDecode([ - DecodeError( - "Field", - "Nothing", - ["latest_stable_version"], - ), - ])), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_version_null_json.accepted b/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_version_null_json.accepted deleted file mode 100644 index 61d860c..0000000 --- a/birdie_snapshots/erlang@decode_latest_stable_version_and_licenses@test_testdata_hex_version_null_json.accepted +++ /dev/null @@ -1,14 +0,0 @@ ---- -version: 1.2.6 -title: Erlang@decode_latest_stable_version_and_licenses@test/testdata/hex/version_null.json ---- -#( - "{ - "meta": { - "licenses": ["bin"] - }, - "latest_stable_version": null -} -", - Ok(HexInfo(None, ["bin"])), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@spin_up_test@ignore_indirect.accepted b/birdie_snapshots/erlang@spin_up_test@ignore_indirect.accepted deleted file mode 100644 index 815bb21..0000000 --- a/birdie_snapshots/erlang@spin_up_test@ignore_indirect.accepted +++ /dev/null @@ -1,22 +0,0 @@ ---- -version: 1.3.1 -title: Erlang@spin_up_test@ignore_indirect ---- -#( - ["--ignore-indirect"], - Config( - [], - False, - True, - False, - Minimal, - False, - True, - Native, - [], - [], - [], - [], - False, - ), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@spin_up_test@outdated.accepted b/birdie_snapshots/erlang@spin_up_test@outdated.accepted deleted file mode 100644 index a4ee155..0000000 --- a/birdie_snapshots/erlang@spin_up_test@outdated.accepted +++ /dev/null @@ -1,22 +0,0 @@ ---- -version: 1.3.1 -title: Erlang@spin_up_test@outdated ---- -#( - ["--outdated"], - Config( - [], - True, - False, - False, - Minimal, - False, - True, - Native, - [], - [], - [], - [], - False, - ), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@warning@outdated_to_warning.accepted b/birdie_snapshots/erlang@warning@outdated_to_warning.accepted deleted file mode 100644 index 71c45c9..0000000 --- a/birdie_snapshots/erlang@warning@outdated_to_warning.accepted +++ /dev/null @@ -1,25 +0,0 @@ ---- -version: 1.3.1 -title: Erlang@warning@outdated_to_warning ---- -#( - #( - Package( - "package for warning tests", - SemVer(1, 2, 3, "pre", "build"), - "pre1.2.3-build", - True, - PackageSourceHex, - ), - "1.2.3", - ), - Warning( - None, - "package for warning tests", - Some("pre1.2.3-build"), - "New Version: '1.2.3' exists", - WarningReasonOutdated, - SeverityPackageOutdated, - DirectDep, - ), -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@warning_format_as_json@outdated_to_warning.accepted b/birdie_snapshots/erlang@warning_format_as_json@outdated_to_warning.accepted deleted file mode 100644 index a5dcd2e..0000000 --- a/birdie_snapshots/erlang@warning_format_as_json@outdated_to_warning.accepted +++ /dev/null @@ -1,27 +0,0 @@ ---- -version: 1.3.1 -title: Erlang@warning_format_as_json@outdated_to_warning ---- -#( - #( - Package( - "package for warning tests", - SemVer(1, 2, 3, "pre", "build"), - "pre1.2.3-build", - True, - PackageSourceHex, - ), - "1.2.3", - ), - " -{ -"id": null, -"package": "package for warning tests", -"version": "pre1.2.3-build", -"warning_reason": "Outdated", -"dependency_type": "Direct", -"severity": "package-outdated", -"reason": "New Version: '1.2.3' exists" -} -", -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@warning_format_as_string@outdated_to_warning.accepted b/birdie_snapshots/erlang@warning_format_as_string@outdated_to_warning.accepted deleted file mode 100644 index 1854bb6..0000000 --- a/birdie_snapshots/erlang@warning_format_as_string@outdated_to_warning.accepted +++ /dev/null @@ -1,24 +0,0 @@ ---- -version: 1.3.1 -title: Erlang@warning_format_as_string@outdated_to_warning ---- -#( - #( - Package( - "package for warning tests", - SemVer(1, 2, 3, "pre", "build"), - "pre1.2.3-build", - True, - PackageSourceHex, - ), - "1.2.3", - ), - "ID: null -Package: package for warning tests -Version: pre1.2.3-build -WarningReason: Outdated -Dependency Type: Direct -Severity: package-outdated -Reason: New Version: '1.2.3' exists -", -) \ No newline at end of file diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@outdated_to_warning.accepted b/birdie_snapshots/erlang@warning_format_as_string_minimal@outdated_to_warning.accepted deleted file mode 100644 index 0d77cd4..0000000 --- a/birdie_snapshots/erlang@warning_format_as_string_minimal@outdated_to_warning.accepted +++ /dev/null @@ -1,18 +0,0 @@ ---- -version: 1.3.1 -title: Erlang@warning_format_as_string_minimal@outdated_to_warning ---- -#( - #( - Package( - "package for warning tests", - SemVer(1, 2, 3, "pre", "build"), - "pre1.2.3-build", - True, - PackageSourceHex, - ), - "1.2.3", - ), - "package for warning tests-pre1.2.3-build: package-outdated -", -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_empty_licenses_json.accepted b/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_empty_licenses_json.accepted deleted file mode 100644 index 912feba..0000000 --- a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_empty_licenses_json.accepted +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: 1.2.6 -title: Javascript@decode_latest_stable_version_and_licenses@test/testdata/hex/empty_licenses.json ---- -#( - "{ - "meta": { - "licenses": [] - }, - "latest_stable_version": "2.1.0" -} -", - Ok(HexInfo( - latest_stable_version: Some("2.1.0"), - licenses: [], - )), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_full_json.accepted b/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_full_json.accepted deleted file mode 100644 index 9c5470d..0000000 --- a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_full_json.accepted +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: 1.2.6 -title: Javascript@decode_latest_stable_version_and_licenses@test/testdata/hex/full.json ---- -#( - "{ - "meta": { - "licenses": ["MIT"] - }, - "latest_stable_version": "2.1.0" -} -", - Ok(HexInfo( - latest_stable_version: Some("2.1.0"), - licenses: ["MIT"], - )), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_multi_license_json.accepted b/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_multi_license_json.accepted deleted file mode 100644 index f2ddf73..0000000 --- a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_multi_license_json.accepted +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: 1.2.6 -title: Javascript@decode_latest_stable_version_and_licenses@test/testdata/hex/multi_license.json ---- -#( - "{ - "meta": { - "licenses": ["foo", "BAR", "baz"] - }, - "latest_stable_version": "2.1.0" -} -", - Ok(HexInfo( - latest_stable_version: Some("2.1.0"), - licenses: ["foo", "BAR", "baz"], - )), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_license_json.accepted b/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_license_json.accepted deleted file mode 100644 index a879f4a..0000000 --- a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_license_json.accepted +++ /dev/null @@ -1,19 +0,0 @@ ---- -version: 1.3.0 -title: Javascript@decode_latest_stable_version_and_licenses@test/testdata/hex/no_license.json ---- -#( - "{ - "meta": { - }, - "latest_stable_version": "2.1.0" -} -", - Error(UnableToDecode([ - DecodeError( - expected: "Field", - found: "Nothing", - path: ["meta", "licenses"], - ), - ])), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_meta_json.accepted b/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_meta_json.accepted deleted file mode 100644 index f233d31..0000000 --- a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_meta_json.accepted +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: 1.3.0 -title: Javascript@decode_latest_stable_version_and_licenses@test/testdata/hex/no_meta.json ---- -#( - "{ - "latest_stable_version": "2.1.0" -} -", - Error(UnableToDecode([ - DecodeError( - expected: "Field", - found: "Nothing", - path: ["meta"], - ), - ])), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_version_json.accepted b/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_version_json.accepted deleted file mode 100644 index 3c6b85f..0000000 --- a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_no_version_json.accepted +++ /dev/null @@ -1,19 +0,0 @@ ---- -version: 1.3.0 -title: Javascript@decode_latest_stable_version_and_licenses@test/testdata/hex/no_version.json ---- -#( - "{ - "meta": { - "licenses": ["bin"] - } -} -", - Error(UnableToDecode([ - DecodeError( - expected: "Field", - found: "Nothing", - path: ["latest_stable_version"], - ), - ])), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_version_null_json.accepted b/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_version_null_json.accepted deleted file mode 100644 index 6e0a154..0000000 --- a/birdie_snapshots/javascript@decode_latest_stable_version_and_licenses@test_testdata_hex_version_null_json.accepted +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: 1.2.6 -title: Javascript@decode_latest_stable_version_and_licenses@test/testdata/hex/version_null.json ---- -#( - "{ - "meta": { - "licenses": ["bin"] - }, - "latest_stable_version": null -} -", - Ok(HexInfo( - latest_stable_version: None, - licenses: ["bin"], - )), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@spin_up_test@ignore_indirect.accepted b/birdie_snapshots/javascript@spin_up_test@ignore_indirect.accepted deleted file mode 100644 index 44fffdc..0000000 --- a/birdie_snapshots/javascript@spin_up_test@ignore_indirect.accepted +++ /dev/null @@ -1,22 +0,0 @@ ---- -version: 1.3.1 -title: Javascript@spin_up_test@ignore_indirect ---- -#( - ["--ignore-indirect"], - Config( - dev_deps: [], - outdated: False, - ignore_indirect: True, - force: False, - format: Minimal, - verbose: False, - global: True, - puller: CURL, - allowed_licenses: [], - ignore_packages: [], - ignore_severity: [], - ignore_ids: [], - ignore_dev_dependencies: False, - ), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@spin_up_test@outdated.accepted b/birdie_snapshots/javascript@spin_up_test@outdated.accepted deleted file mode 100644 index c18bd43..0000000 --- a/birdie_snapshots/javascript@spin_up_test@outdated.accepted +++ /dev/null @@ -1,22 +0,0 @@ ---- -version: 1.3.1 -title: Javascript@spin_up_test@outdated ---- -#( - ["--outdated"], - Config( - dev_deps: [], - outdated: True, - ignore_indirect: False, - force: False, - format: Minimal, - verbose: False, - global: True, - puller: CURL, - allowed_licenses: [], - ignore_packages: [], - ignore_severity: [], - ignore_ids: [], - ignore_dev_dependencies: False, - ), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@warning@outdated_to_warning.accepted b/birdie_snapshots/javascript@warning@outdated_to_warning.accepted deleted file mode 100644 index dc1a280..0000000 --- a/birdie_snapshots/javascript@warning@outdated_to_warning.accepted +++ /dev/null @@ -1,31 +0,0 @@ ---- -version: 1.3.1 -title: Javascript@warning@outdated_to_warning ---- -#( - #( - Package( - name: "package for warning tests", - version: SemVer( - major: 1, - minor: 2, - patch: 3, - pre: "pre", - build: "build", - ), - version_raw: "pre1.2.3-build", - direct: True, - source: PackageSourceHex, - ), - "1.2.3", - ), - Warning( - advisory_id: None, - package: "package for warning tests", - version: Some("pre1.2.3-build"), - reason: "New Version: '1.2.3' exists", - warning_reason_code: WarningReasonOutdated, - severity: SeverityPackageOutdated, - dep: DirectDep, - ), -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@warning_format_as_json@outdated_to_warning.accepted b/birdie_snapshots/javascript@warning_format_as_json@outdated_to_warning.accepted deleted file mode 100644 index 4a7bd49..0000000 --- a/birdie_snapshots/javascript@warning_format_as_json@outdated_to_warning.accepted +++ /dev/null @@ -1,31 +0,0 @@ ---- -version: 1.3.1 -title: Javascript@warning_format_as_json@outdated_to_warning ---- -#( - #( - Package( - name: "package for warning tests", - version: SemVer( - major: 1, - minor: 2, - patch: 3, - pre: "pre", - build: "build", - ), - version_raw: "pre1.2.3-build", - direct: True, - source: PackageSourceHex, - ), - "1.2.3", - ), - "{ - "id": null, - "package": "package for warning tests", - "version": "pre1.2.3-build", - "warning_reason": "Outdated", - "dependency_type": "Direct", - "severity": "package-outdated", - "reason": "New Version: '1.2.3' exists" -}", -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@warning_format_as_string@outdated_to_warning.accepted b/birdie_snapshots/javascript@warning_format_as_string@outdated_to_warning.accepted deleted file mode 100644 index 6064373..0000000 --- a/birdie_snapshots/javascript@warning_format_as_string@outdated_to_warning.accepted +++ /dev/null @@ -1,30 +0,0 @@ ---- -version: 1.3.1 -title: Javascript@warning_format_as_string@outdated_to_warning ---- -#( - #( - Package( - name: "package for warning tests", - version: SemVer( - major: 1, - minor: 2, - patch: 3, - pre: "pre", - build: "build", - ), - version_raw: "pre1.2.3-build", - direct: True, - source: PackageSourceHex, - ), - "1.2.3", - ), - "ID: null -Package: package for warning tests -Version: pre1.2.3-build -WarningReason: Outdated -Dependency Type: Direct -Severity: package-outdated -Reason: New Version: '1.2.3' exists -", -) \ No newline at end of file diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@outdated_to_warning.accepted b/birdie_snapshots/javascript@warning_format_as_string_minimal@outdated_to_warning.accepted deleted file mode 100644 index 23b3851..0000000 --- a/birdie_snapshots/javascript@warning_format_as_string_minimal@outdated_to_warning.accepted +++ /dev/null @@ -1,24 +0,0 @@ ---- -version: 1.3.1 -title: Javascript@warning_format_as_string_minimal@outdated_to_warning ---- -#( - #( - Package( - name: "package for warning tests", - version: SemVer( - major: 1, - minor: 2, - patch: 3, - pre: "pre", - build: "build", - ), - version_raw: "pre1.2.3-build", - direct: True, - source: PackageSourceHex, - ), - "1.2.3", - ), - "package for warning tests-pre1.2.3-build: package-outdated -", -) \ No newline at end of file diff --git a/dev/go_over_dev.gleam b/dev/go_over_dev.gleam index cc16c49..331f5da 100644 --- a/dev/go_over_dev.gleam +++ b/dev/go_over_dev.gleam @@ -72,6 +72,11 @@ pub fn main() { gfunction.iff_nil( True, - gfunction.freeze2(go_over.print_warnings, example_warnings, conf), + gfunction.freeze3( + go_over.print_warnings, + example_warnings, + conf, + option.None, + ), ) } diff --git a/gleam.toml b/gleam.toml index 585c8a4..60d96d8 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "go_over" -version = "3.3.0" +version = "4.0.0" licences = ["MIT"] repository = { type = "github", user = "bwireman", repo = "go-over" } description = "A tool to audit Erlang & Elixir dependencies, to make sure your Gleam ✨ projects really sparkle!" @@ -51,15 +51,14 @@ actions = [ { command = "./scripts/update.sh", kind = "binary" }, { command = "./scripts/target_test.sh", kind = "binary", args = ["erlang"], files = [".erl", ".gleam", "manifest.toml", "gleam.toml"] }, { command = "./scripts/target_test.sh", kind = "binary", args = ["javascript", "node"], files = [".mjs", ".js", ".gleam", "manifest.toml", "gleam.toml"] }, - { command = "./scripts/target_test.sh", kind = "binary", args = ["javascript", "deno"], files = [".mjs", ".js", ".gleam", "manifest.toml", "gleam.toml"] }, - { command = "./scripts/target_test.sh", kind = "binary", args = ["javascript", "bun"], files = [".mjs", ".js", ".gleam", "manifest.toml", "gleam.toml"] }, + { command = "./scripts/target_test.sh", kind = "binary", args = ["javascript", "deno"], files = [".mjs", ".js", "manifest.toml", "gleam.toml"] }, + { command = "./scripts/target_test.sh", kind = "binary", args = ["javascript", "bun"], files = [".mjs", ".js", "manifest.toml", "gleam.toml"] }, ] [go-over] -cache = true +force = false global = true format = "minimal" -outdated = false puller = "native" allowed_licenses = ["MIT", "Apache-2.0", "BSD 2-Clause", "WTFPL"] diff --git a/images/demo.gif b/images/demo.gif index acfa05d..5232062 100644 Binary files a/images/demo.gif and b/images/demo.gif differ diff --git a/images/demo.tape b/images/demo.tape index 1b93a07..ad7d9b8 100644 --- a/images/demo.tape +++ b/images/demo.tape @@ -1,5 +1,5 @@ Output images/demo.gif -Set FontSize 14 +Set FontSize 12 Set PlaybackSpeed 1.35 Set TypingSpeed 75ms Set FontFamily 'JetBrains Mono' @@ -9,7 +9,9 @@ Set BorderRadius 12 # Setup Hide -Type `rm -rf ~/.cache/go-over/ && clear` +Type `rm -rf ~/.cache/go-over/deps/g* ~/.cache/go-over/package-licenses/g*` +Enter +Type `gleam build && clear` Enter Show @@ -21,10 +23,10 @@ Sleep 10s Type clear Enter -Type "# check for retired, vulnerable or outdated packages (and even filter their licenses)" +Type "# check for retired or vulnerable packages (and even filter their licenses)" Enter Sleep 5s -Type `gleam run -m go_over -- --outdated` +Type `gleam run -m go_over` Enter Wait+Screen@50s /✅ No warnings found!/ Sleep 3.5s \ No newline at end of file diff --git a/manifest.toml b/manifest.toml index 7ba0c9c..cee9d65 100644 --- a/manifest.toml +++ b/manifest.toml @@ -29,7 +29,7 @@ packages = [ { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1F543AFBA5D33DA493E6087F4E4C4F20D899411343512686C98A8ABB2963CF22" }, { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, { name = "gleamsver", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleamsver", source = "hex", outer_checksum = "EA74FDC66BF15CB2CF4F8FF9B6FA01D511712EE2B1F4BE0371076ED3F685EEAE" }, - { name = "glearray", version = "2.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "C013E0F153E73F5D35609AA6F2570D41D2E35C0DD404E91E847859EE41CBC139" }, + { name = "glearray", version = "2.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "1554E48DD40114D7602F5BFF4D7278B6B3B735F137C7FDEEADFB2FE7951C94BE" }, { name = "gleave", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "gleave", source = "hex", outer_checksum = "E34C23F8AD68A3DB19F19EE044193137B0245CB5CEFF83B3B310BD584BF74A07" }, { name = "gleeunit", version = "1.10.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "254B697FE72EEAD7BF82E941723918E421317813AC49923EE76A18C788C61E72" }, { name = "gleither", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleither", source = "hex", outer_checksum = "4CF283FE767D204A58557F5A5EA3EB247AAEF802B4E57297429F7543D368875C" }, diff --git a/scripts/target_test.sh b/scripts/target_test.sh index 744271b..dbea87f 100755 --- a/scripts/target_test.sh +++ b/scripts/target_test.sh @@ -31,14 +31,15 @@ else CMD="--target javascript --runtime $RUNTIME" fi +# shellcheck disable=SC2086 +gleam run $CMD -- --help # shellcheck disable=SC2086 gleam run $CMD -- --force --verbose --puller $PULLER -rm -rf .go-over/outdated snooze 15 # shellcheck disable=SC2086 -gleam run $CMD -- --outdated --puller wget --local +gleam run $CMD -- --puller wget --local # shellcheck disable=SC2086 gleam test $CMD diff --git a/scripts/update.sh b/scripts/update.sh index 0307295..0bd8515 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -4,5 +4,5 @@ set -e cd "$(dirname "$0")/.." gleam update -gleam run -- --outdated +gleam run -- npm upgrade --ignore-scripts \ No newline at end of file diff --git a/src/go_over.gleam b/src/go_over.gleam index 52b44ca..1df9c86 100644 --- a/src/go_over.gleam +++ b/src/go_over.gleam @@ -1,10 +1,15 @@ +import filepath import gleam/int import gleam/io import gleam/json import gleam/list +import gleam/option +import gleam/result import gleam/string -import go_over/config.{type Config} +import go_over/advisories/advisories +import go_over/config.{type Config, type Flags} import go_over/packages +import go_over/sarif import go_over/sources import go_over/util/constants import go_over/util/globals @@ -12,123 +17,437 @@ import go_over/util/print import go_over/util/spinner import go_over/util/util import go_over/warning.{type Warning} +import go_over/workspace import gxyz/function as gfunction import shellout import simplifile -fn print_warnings_count(vulns: List(Warning)) -> List(Warning) { - { - "⛔ " - <> int.to_string(list.length(vulns)) - <> " WARNING(s) FOUND!" - <> constants.long_ass_dashes +pub type AuditResult { + AuditResult( + project_root: String, + fatal_warnings: List(Warning), + info_warnings: List(Warning), + outdated_failed: Bool, + format: config.Format, + ) +} + +fn prefix_label(prefix: option.Option(String), label: String) -> String { + case prefix { + option.Some(path) -> "[" <> path <> "] " <> label + option.None -> label } - |> io.print_error() - vulns } -pub fn print_warnings(vulns: List(Warning), conf: Config) -> Nil { +fn print_warnings_output( + vulns: List(Warning), + conf: Config, + label: String, + prefix: option.Option(String), +) -> Nil { + let label = prefix_label(prefix, label) + io.print_error(label) + case conf.format { config.Minimal -> vulns - |> print_warnings_count |> list.map(warning.format_as_string_minimal) |> string.join("") |> io.print_error() - config.JSON -> - vulns - |> list.map(warning.format_as_json) - |> json.preprocessed_array() - |> json.to_string() - |> io.print_error() + config.JSON -> Nil + + config.SARIF -> Nil _ -> vulns - |> print_warnings_count |> list.map(warning.format_as_string) |> string.join(constants.long_ass_dashes) |> io.print_error() } +} + +fn warnings_label(vulns: List(Warning), kind: String) -> String { + "⛔ " + <> int.to_string(list.length(vulns)) + <> " " + <> kind + <> "(s) FOUND!" + <> constants.long_ass_dashes +} + +fn info_label(vulns: List(Warning)) -> String { + "â„šī¸ " + <> int.to_string(list.length(vulns)) + <> " Item(s) of Note" + <> constants.long_ass_dashes +} + +fn warnings_for_json(result: AuditResult) -> List(Warning) { + list.append(result.info_warnings, result.fatal_warnings) +} + +pub fn warnings_for_json_results(results: List(AuditResult)) -> List(Warning) { + list.flat_map(results, warnings_for_json) +} + +pub fn print_json_warnings(warnings: List(Warning)) -> Nil { + warnings + |> list.map(warning.format_as_json) + |> json.preprocessed_array() + |> json.to_string() + |> io.print_error() +} + +fn write_sarif( + results: List(AuditResult), + output: option.Option(String), +) -> Nil { + let runs = + list.map(results, fn(result) { + #(result.project_root, warnings_for_json(result)) + }) + + let content = + sarif.to_sarif_log(runs) + |> json.to_string() + + case output { + option.None -> content |> io.println() + option.Some(path) -> + case path |> simplifile.write(content) { + Ok(Nil) -> Nil + Error(_) -> { + io.println_error("could not write SARIF output to " <> path) + shellout.exit(1) + } + } + } +} + +pub fn skipped_workspace_warnings(skipped: List(String)) -> List(Warning) { + list.map(skipped, fn(path) { + warning.info_to_warning( + path, + "Info: project at '" + <> path + <> "' was skipped (exceeds workspace_max_depth)", + ) + }) +} + +pub fn print_warnings( + vulns: List(Warning), + conf: Config, + prefix: option.Option(String), +) -> Nil { + print_warnings_output(vulns, conf, warnings_label(vulns, "WARNING"), prefix) shellout.exit(1) } -pub fn main() { - let conf = case - config.spin_up(config.read_config("gleam.toml"), shellout.arguments()) - { - Error(e) -> { - io.println_error(e) - shellout.exit(0) - util.do_panic() - } - Ok(conf) -> conf +fn read_project_config(project_root: String) -> Config { + let gleam_toml = filepath.join(project_root, "gleam.toml") + + case simplifile.read(gleam_toml) { + Ok(_) -> config.read_config(gleam_toml) + Error(_) -> config.default_config() } +} + +pub fn audit_project( + flags: Flags, + project_root: String, +) -> Result(AuditResult, String) { + let manifest_toml = filepath.join(project_root, "manifest.toml") + let project_config = read_project_config(project_root) + + use conf <- result.try(config.merge_flags_and_config(flags, project_config)) + globals.set_project_root(project_root) globals.set_verbose(conf.verbose) globals.set_use_global_cache(conf.global) globals.set_force(conf.force) - let spinner = spinner.new_spinner("Let's do this!") + let machine_output = conf.format == config.SARIF || conf.format == config.JSON + let spinner = case machine_output { + True -> option.None + False -> spinner.new_spinner("Let's do this!") + } + gfunction.ignore_result( conf.force, gfunction.freeze1(simplifile.delete, globals.go_over_path()), ) spinner.set_text_spinner(spinner, "Reading manifest") - let pkgs = - packages.read_manifest("manifest.toml") + let manifest_pkgs = packages.read_manifest(manifest_toml) + let pkgs_audited = + manifest_pkgs |> config.filter_dev_dependencies(conf, _) - |> config.filter_packages(conf, _) |> config.filter_indirect(conf, _) + let hex_pkgs = + list.filter(pkgs_audited, fn(p) { p.source == packages.PackageSourceHex }) + spinner.set_text_spinner( spinner, "Checking packages: " <> print.raw("vulnerable", "red"), ) - let vulnerable_warnings = sources.get_vulnerable_warnings(pkgs, conf) + let all_advisories = advisories.fetch_all() + let vulnerable_warnings = + sources.get_vulnerable_warnings(pkgs_audited, conf, all_advisories) spinner.set_text_spinner( spinner, "Checking packages: " <> print.raw("retired", "yellow"), ) - let retired_warnings = - pkgs - |> list.filter(fn(p) { p.source == packages.PackageSourceHex }) - |> sources.get_retired_warnings(conf) - - let hex_warnings = - gfunction.iff( - conf.outdated || !list.is_empty(conf.allowed_licenses), - fn() { - let msg = case conf.outdated, !list.is_empty(conf.allowed_licenses) { - True, True -> "outdated & licenses" - True, False -> "outdated" - False, True -> "licenses" - False, False -> util.do_panic() - } + let retired_warnings = sources.get_retired_warnings(hex_pkgs, conf) - spinner.set_text_spinner( - spinner, - "Checking packages: " <> print.raw(msg, "brightmagenta"), - ) + let #(hex_warnings, dependency_licenses) = case conf.allowed_licenses { + [] -> #([], []) + _ -> { + spinner.set_text_spinner( + spinner, + "Checking packages: " <> print.raw("licenses", "brightmagenta"), + ) - pkgs - |> list.filter(fn(p) { p.source == packages.PackageSourceHex }) - |> sources.get_hex_warnings(conf) - }, - [], - ) + sources.get_hex_warnings(hex_pkgs, conf) + } + } spinner.set_text_spinner(spinner, "Filtering warnings") - let warnings = + let audit_warnings = list.append(retired_warnings, vulnerable_warnings) |> list.append(hex_warnings) + + let unnecessary_warnings = + config.unnecessary_ignore_warnings( + conf, + manifest_pkgs, + audit_warnings, + dependency_licenses, + all_advisories, + ) + + let fatal_warnings = + audit_warnings + |> config.filter_package_warnings(conf, _) |> config.filter_severity(conf, _) spinner.stop_spinner(spinner) - case warnings { - [] -> print.success("✅ No warnings found!") - vulns -> print_warnings(vulns, conf) + + let outdated_failed = case conf.outdated { + False -> False + True -> run_deps_outdated(project_root) + } + + let info_warnings = + list.append( + unnecessary_warnings, + warning.git_deps_to_warnings(manifest_pkgs), + ) + + Ok(AuditResult( + project_root:, + fatal_warnings:, + info_warnings:, + outdated_failed:, + format: conf.format, + )) +} + +pub fn main() { + case config.parse_flags(shellout.arguments()) { + Error(message) -> io.println(message) + Ok(flags) -> run(flags) + } +} + +fn print_no_issues_success(info_count: Int) -> Nil { + case info_count { + 0 -> print.success("✅ No warnings found!") + _ -> + print.success( + "✅ No security issues found (" + <> int.to_string(info_count) + <> " item(s) of note)", + ) + } +} + +fn run(flags: config.Flags) -> Nil { + let workspace_mode = option.is_some(flags.workspace_root) + + let #(results, workspace_skipped) = case flags.workspace_root { + option.Some(scan_root) -> { + let scan_config = read_project_config(scan_root) + let max_depth = scan_config.workspace_max_depth + + case workspace.discover_or_error(scan_root, max_depth) { + Ok(workspace.DiscoverResult(projects, skipped)) -> #( + list.map(projects, fn(project_root) { + case audit_project(flags, project_root) { + Ok(result) -> result + Error(e) -> { + io.println_error(e) + shellout.exit(1) + util.do_panic() + } + } + }), + skipped, + ) + Error(e) -> { + io.println_error(e) + shellout.exit(1) + util.do_panic() + } + } + } + option.None -> { + let project_root = option.unwrap(flags.single_root, ".") + let assert Ok(result) = audit_project(flags, project_root) + #([result], []) + } + } + + let skipped_warnings = skipped_workspace_warnings(workspace_skipped) + let results = case skipped_warnings { + [] -> results + _ -> { + case results { + [first, ..rest] -> [ + AuditResult( + ..first, + info_warnings: list.append(skipped_warnings, first.info_warnings), + ), + ..rest + ] + [] -> results + } + } + } + + case workspace_mode, flags.format { + True, option.None -> + case + config.validate_workspace_formats( + list.map(results, fn(r) { #(r.format, r.project_root) }), + ) + { + Error(e) -> { + io.println_error(e) + shellout.exit(1) + util.do_panic() + } + Ok(Nil) -> Nil + } + _, _ -> Nil + } + + let prefix_for = fn(result: AuditResult) { + case workspace_mode { + True -> option.Some(result.project_root) + False -> option.None + } + } + + let display_conf = fn(result: AuditResult) { + config.Config(..config.default_config(), format: result.format) + } + + let output_format = case results { + [] -> config.Minimal + [first, ..] -> first.format + } + + let sarif_output = output_format == config.SARIF + let json_output = output_format == config.JSON + + case flags.sarif_output, sarif_output { + option.Some(_), False -> { + io.println_error("--sarif-output requires --format sarif") + shellout.exit(1) + } + _, _ -> Nil + } + + case sarif_output, json_output { + False, False -> + list.each(results, fn(result) { + case result.info_warnings { + [] -> Nil + info -> + print_warnings_output( + info, + display_conf(result), + info_label(info), + prefix_for(result), + ) + } + }) + False, True -> print_json_warnings(warnings_for_json_results(results)) + True, _ -> Nil + } + + let any_fatal = list.any(results, fn(r) { !list.is_empty(r.fatal_warnings) }) + let any_outdated_failed = list.any(results, fn(r) { r.outdated_failed }) + let info_count = + list.flat_map(results, fn(r) { r.info_warnings }) |> list.length + + case any_fatal, any_outdated_failed, sarif_output, json_output { + False, False, True, _ -> { + write_sarif(results, flags.sarif_output) + Nil + } + False, False, False, _ -> { + print_no_issues_success(info_count) + Nil + } + False, True, True, _ -> { + write_sarif(results, flags.sarif_output) + shellout.exit(1) + } + False, True, False, _ -> shellout.exit(1) + True, _, True, _ -> { + write_sarif(results, flags.sarif_output) + shellout.exit(1) + } + True, _, False, True -> shellout.exit(1) + True, _, False, False -> { + list.each(results, fn(result) { + case result.fatal_warnings { + [] -> Nil + vulns -> + print_warnings_output( + vulns, + display_conf(result), + warnings_label(vulns, "WARNING"), + prefix_for(result), + ) + } + }) + shellout.exit(1) + } + } +} + +fn run_deps_outdated(project_root: String) -> Bool { + print.high( + "The --outdated flag is deprecated. Use `gleam deps outdated` instead.", + ) + + case + shellout.command( + run: "gleam", + with: ["deps", "outdated"], + in: project_root, + opt: [ + shellout.LetBeStdout, + ], + ) + { + Ok(_) -> False + Error(_) -> True } } diff --git a/src/go_over/advisories/advisories.gleam b/src/go_over/advisories/advisories.gleam index 3461d5a..6dff3db 100644 --- a/src/go_over/advisories/advisories.gleam +++ b/src/go_over/advisories/advisories.gleam @@ -1,7 +1,6 @@ import filepath import gleam/list import gleam/option.{None, Some} -import global_value import go_over/advisories/comparisons import go_over/packages.{type Package} import go_over/util/cache @@ -23,10 +22,8 @@ pub type Advisory { } fn advisories_path() -> String { - global_value.create_with_unique_name("advisories.path.global.data", fn() { - globals.go_over_path() - |> filepath.join("mirego-elixir-security-advisories") - }) + globals.go_over_path() + |> filepath.join("mirego-elixir-security-advisories") } @external(erlang, "go_over@ffi", "parse_adv") @@ -125,9 +122,7 @@ fn delete_and_clone() -> Nil { |> list.each(simplifile.delete) } -pub fn check_for_advisories( - packages: List(packages.Package), -) -> List(#(Package, List(Advisory))) { +pub fn fetch_all() -> List(Advisory) { cache.pull_if_not_cached( advisories_path(), six_hours, @@ -135,8 +130,13 @@ pub fn check_for_advisories( constants.advisories_repo, ) - let advisories = read_all_adv() + read_all_adv() +} +pub fn check_for_advisories( + packages: List(packages.Package), + advisories: List(Advisory), +) -> List(#(Package, List(Advisory))) { list.map(packages, fn(pkg) { case is_vulnerable(pkg, advisories) { [] -> None diff --git a/src/go_over/config.gleam b/src/go_over/config.gleam index 51de754..8ec1199 100644 --- a/src/go_over/config.gleam +++ b/src/go_over/config.gleam @@ -2,10 +2,9 @@ import clip import clip/arg_info import clip/flag import clip/help -import clip/opt import gleam/dict import gleam/list -import gleam/option.{type Option, Some} +import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import go_over/advisories/advisories.{type Advisory} @@ -25,6 +24,7 @@ pub type Format { Minimal Detailed JSON + SARIF } pub type Config { @@ -42,6 +42,9 @@ pub type Config { ignore_severity: List(String), ignore_ids: List(String), ignore_dev_dependencies: Bool, + workspace_max_depth: Int, + single_root: Option(String), + workspace_root: Option(String), ) } @@ -49,15 +52,49 @@ pub type Flags { Flags( force: Bool, outdated: Bool, - ignore_indirect: Bool, global: Bool, local: Bool, verbose: Bool, format: option.Option(Format), puller: option.Option(puller.Puller), + single_root: option.Option(String), + workspace_root: option.Option(String), + sarif_output: option.Option(String), + ) +} + +pub fn default_config() -> Config { + Config( + dev_deps: [], + outdated: False, + ignore_indirect: False, + force: False, + format: Minimal, + verbose: False, + global: True, + puller: puller.default, + allowed_licenses: [], + ignore_packages: [], + ignore_severity: [], + ignore_ids: [], + ignore_dev_dependencies: False, + workspace_max_depth: 3, + single_root: option.None, + workspace_root: option.None, ) } +fn read_dev_dependency_names(gleam: dict.Dict(String, Toml)) -> List(String) { + let hyphen = + tom.get_table(gleam, ["dev-dependencies"]) + |> result.unwrap(dict.new()) + let underscore = + tom.get_table(gleam, ["dev_dependencies"]) + |> result.unwrap(dict.new()) + + dict.merge(hyphen, underscore) |> dict.keys() +} + pub fn read_config(path: String) -> Config { let res = simplifile.read(path) @@ -65,18 +102,18 @@ pub fn read_config(path: String) -> Config { let gleam = tom.parse(res) |> cli.hard_fail_with_msg("could not read config file at " <> path) - let dev_deps = - tom.get_table(gleam, ["dev-dependencies"]) - |> result.unwrap(dict.new()) - |> dict.keys() + let dev_deps = read_dev_dependency_names(gleam) let go_over = tom.get_table(gleam, ["go-over"]) |> result.unwrap(dict.new()) - let cache = - tom.get_bool(go_over, ["cache"]) - |> result.unwrap(True) + let force = + tom.get_bool(go_over, ["force"]) + |> result.unwrap(False) + let workspace_max_depth = + tom.get_int(go_over, ["workspace_max_depth"]) + |> result.unwrap(3) let outdated = tom.get_bool(go_over, ["outdated"]) |> result.unwrap(False) @@ -130,8 +167,7 @@ pub fn read_config(path: String) -> Config { dev_deps:, outdated:, ignore_indirect:, - force: !cache, - //read from flags only + force:, verbose: False, global:, puller:, @@ -141,6 +177,9 @@ pub fn read_config(path: String) -> Config { ignore_severity:, ignore_ids:, ignore_dev_dependencies:, + workspace_max_depth:, + single_root: option.None, + workspace_root: option.None, ) } @@ -172,6 +211,13 @@ pub fn filter_advisory_ids( glist.reject_contains_tap(advisories, fn(adv) { adv.id }, conf.ignore_ids) } +pub fn filter_package_warnings( + conf: Config, + warnings: List(Warning), +) -> List(Warning) { + glist.reject_contains_tap(warnings, fn(w) { w.package }, conf.ignore_packages) +} + pub fn filter_severity(conf: Config, warnings: List(Warning)) -> List(Warning) { glist.reject_contains_tap( warnings, @@ -180,16 +226,157 @@ pub fn filter_severity(conf: Config, warnings: List(Warning)) -> List(Warning) { ) } +pub fn unnecessary_ignore_warnings( + conf: Config, + manifest_pkgs: List(Package), + audit_warnings: List(Warning), + dependency_licenses: List(String), + all_advisories: List(Advisory), +) -> List(Warning) { + let manifest_names = list.map(manifest_pkgs, fn(pkg) { pkg.name }) + let packages_with_warnings = list.map(audit_warnings, fn(w) { w.package }) + let severities_present = + audit_warnings + |> list.map(fn(w) { + string.lowercase(warning.severity_as_string(w.severity)) + }) + + let package_warnings = + conf.ignore_packages + |> list.flat_map(fn(name) { + case list.contains(manifest_names, name) { + False -> [ + warning.info_to_warning( + name, + "Info: package '" <> name <> "' is not a dependency", + ), + ] + True -> + case list.contains(packages_with_warnings, name) { + True -> [] + False -> [ + warning.info_to_warning( + name, + "Info: package '" <> name <> "' did not match any warnings", + ), + ] + } + } + }) + + let license_warnings = + conf.allowed_licenses + |> list.filter(fn(license) { !list.contains(dependency_licenses, license) }) + |> list.map(fn(license) { + warning.info_to_warning( + license, + "Info: license '" + <> license + <> "' did not match any dependency licenses", + ) + }) + + let severity_warnings = + conf.ignore_severity + |> list.filter(fn(sev) { + !list.contains(severities_present, string.lowercase(sev)) + }) + |> list.map(fn(sev) { + warning.info_to_warning( + sev, + "Info: severity '" <> sev <> "' did not match any warnings", + ) + }) + + let id_warnings = + unnecessary_ignore_id_warnings(conf, manifest_names, all_advisories) + + let indirect_warnings = case conf.ignore_indirect { + False -> [] + True -> + case list.any(manifest_pkgs, fn(pkg) { !pkg.direct }) { + True -> [] + False -> [ + warning.info_to_warning( + "indirect", + "Info: indirect=true has no effect (no indirect dependencies)", + ), + ] + } + } + + let dev_dep_warnings = case conf.ignore_dev_dependencies { + False -> [] + True -> + case conf.dev_deps { + [] -> [ + warning.info_to_warning( + "dev_dependencies", + "Info: dev_dependencies=true has no effect (no dev-dependencies configured)", + ), + ] + dev_deps -> + case list.any(dev_deps, list.contains(manifest_names, _)) { + True -> [] + False -> [ + warning.info_to_warning( + "dev_dependencies", + "Info: dev_dependencies=true has no effect (no dev-dependencies in manifest)", + ), + ] + } + } + } + + package_warnings + |> list.append(license_warnings) + |> list.append(severity_warnings) + |> list.append(id_warnings) + |> list.append(indirect_warnings) + |> list.append(dev_dep_warnings) +} + +pub fn unnecessary_ignore_id_warnings( + conf: Config, + manifest_names: List(String), + all_advisories: List(Advisory), +) -> List(Warning) { + conf.ignore_ids + |> list.flat_map(fn(id) { + case list.find(all_advisories, fn(a: Advisory) { a.id == id }) { + Error(_) -> [ + warning.info_to_warning( + id, + "Info: advisory id '" <> id <> "' is unknown", + ), + ] + Ok(adv) -> + case list.contains(manifest_names, adv.name) { + True -> [] + False -> [ + warning.info_to_warning( + id, + "Info: advisory id '" + <> id + <> "' does not apply to any dependency", + ), + ] + } + } + }) +} + pub fn parse_config_format(val: String) -> option.Option(Format) { case string.lowercase(val) { "json" -> option.Some(JSON) + "sarif" -> option.Some(SARIF) "detailed" -> option.Some(Detailed) "minimal" -> option.Some(Minimal) format -> { print.warning( "Invalid format '" <> format - <> "' valid options are ['json', 'detailed', 'minimal'], defaulting to minimal", + <> "' valid options are ['json', 'sarif', 'detailed', 'minimal'], defaulting to minimal", ) option.None } @@ -231,9 +418,48 @@ const logo = " ____ ____ ____ _ _____ _____ \\__, /\\____/____\\____/|___/\\___/_/ /____/ /_____/" +const help_named_opts = [ + arg_info.NamedInfo( + name: "format", + short: option.None, + default: option.None, + help: option.Some( + "Specify the output format of any warnings, [minimal, detailed, json, sarif]", + ), + ), + arg_info.NamedInfo( + name: "sarif-output", + short: option.None, + default: option.None, + help: option.Some( + "Write SARIF output to PATH instead of stdout (requires --format sarif)", + ), + ), + arg_info.NamedInfo( + name: "puller", + short: option.None, + default: option.None, + help: option.Some( + "Specify the tool used to reach out to hex.pm, [native, curl, wget, httpie]", + ), + ), + arg_info.NamedInfo( + name: "root", + short: option.None, + default: option.None, + help: option.Some("Audit a single Gleam project at PATH"), + ), + arg_info.NamedInfo( + name: "workspace", + short: option.None, + default: option.None, + help: option.Some("Audit all Gleam projects under PATH (defaults to .)"), + ), +] + fn help_message(args: arg_info.ArgInfo) -> String { arg_info.ArgInfo( - named: args.named, + named: list.append(help_named_opts, args.named), positional: args.positional, flags: args.flags, subcommands: args.subcommands, @@ -247,6 +473,19 @@ fn help_message(args: arg_info.ArgInfo) -> String { |> string.crop(" ") } +pub fn normalize_workspace_argv(argv: List(String)) -> List(String) { + case argv { + ["--workspace", next, ..rest] -> + case string.starts_with(next, "-") { + True -> ["--workspace", ".", next, ..rest] + False -> argv + } + ["--workspace", ..rest] -> ["--workspace", ".", ..rest] + [head, ..rest] -> [head, ..normalize_workspace_argv(rest)] + [] -> [] + } +} + pub fn merge_flags_and_config( flags: Flags, cfg: Config, @@ -257,6 +496,12 @@ pub fn merge_flags_and_config( } use _ <- result.try(invalid) + let invalid = case flags.single_root, flags.workspace_root { + option.Some(_), option.Some(_) -> Error("cannot set --root and --workspace") + _, _ -> Ok(Nil) + } + use _ <- result.try(invalid) + let global = case flags.global, flags.local, cfg.global { True, False, _ -> True False, True, _ -> False @@ -268,7 +513,7 @@ pub fn merge_flags_and_config( dev_deps: cfg.dev_deps, force: flags.force || cfg.force, outdated: flags.outdated || cfg.outdated, - ignore_indirect: cfg.ignore_indirect || flags.ignore_indirect, + ignore_indirect: cfg.ignore_indirect, verbose: flags.verbose, allowed_licenses: cfg.allowed_licenses, puller: option.unwrap(flags.puller, cfg.puller), @@ -278,32 +523,93 @@ pub fn merge_flags_and_config( ignore_severity: cfg.ignore_severity, ignore_ids: cfg.ignore_ids, ignore_dev_dependencies: cfg.ignore_dev_dependencies, + workspace_max_depth: cfg.workspace_max_depth, + single_root: flags.single_root, + workspace_root: flags.workspace_root, )) } -pub fn spin_up(cfg: Config, argv: List(String)) -> Result(Config, String) { +pub fn validate_workspace_formats( + results: List(#(Format, String)), +) -> Result(Nil, String) { + case results { + [] -> Ok(Nil) + [#(first, _), ..rest] -> { + case list.any(rest, fn(r) { r.0 != first }) { + True -> + Error( + "workspace projects have mismatched output formats; set format consistently or use --format", + ) + False -> Ok(Nil) + } + } + } +} + +fn take_named_opts( + argv: List(String), +) -> #( + Option(String), + Option(String), + Option(String), + Option(String), + Option(String), + List(String), +) { + case argv { + ["--format", value, ..rest] -> { + let #(_, puller, root, workspace, sarif_output, remaining) = + take_named_opts(rest) + #(Some(value), puller, root, workspace, sarif_output, remaining) + } + ["--puller", value, ..rest] -> { + let #(format, _, root, workspace, sarif_output, remaining) = + take_named_opts(rest) + #(format, Some(value), root, workspace, sarif_output, remaining) + } + ["--root", value, ..rest] -> { + let #(format, puller, _, workspace, sarif_output, remaining) = + take_named_opts(rest) + #(format, puller, Some(value), workspace, sarif_output, remaining) + } + ["--workspace", value, ..rest] -> { + let #(format, puller, root, _, sarif_output, remaining) = + take_named_opts(rest) + #(format, puller, root, Some(value), sarif_output, remaining) + } + ["--sarif-output", value, ..rest] -> { + let #(format, puller, root, workspace, _, remaining) = + take_named_opts(rest) + #(format, puller, root, workspace, Some(value), remaining) + } + [head, ..rest] -> { + let #(format, puller, root, workspace, sarif_output, remaining) = + take_named_opts(rest) + #(format, puller, root, workspace, sarif_output, [head, ..remaining]) + } + [] -> #(None, None, None, None, None, []) + } +} + +fn clip_command() { clip.command({ use force <- clip.parameter use outdated <- clip.parameter - use ignore_indirect <- clip.parameter use global <- clip.parameter use local <- clip.parameter use verbose <- clip.parameter - use format <- clip.parameter - use puller <- clip.parameter - - merge_flags_and_config( - Flags( - force:, - outdated:, - ignore_indirect:, - verbose:, - format:, - global:, - local:, - puller:, - ), - cfg, + + Flags( + force:, + outdated:, + verbose:, + format: option.None, + global:, + local:, + puller: option.None, + single_root: option.None, + workspace_root: option.None, + sarif_output: option.None, ) }) |> clip.flag(flag.help( @@ -312,11 +618,7 @@ pub fn spin_up(cfg: Config, argv: List(String)) -> Result(Config, String) { )) |> clip.flag(flag.help( flag.new("outdated"), - "Additionally check if newer versions of dependencies exist", - )) - |> clip.flag(flag.help( - flag.new("ignore-indirect"), - "Ignore all warnings for indirect dependencies", + "[deprecated] Use `gleam deps outdated` to check for newer dependency versions", )) |> clip.flag(flag.help( flag.new("global"), @@ -324,29 +626,38 @@ pub fn spin_up(cfg: Config, argv: List(String)) -> Result(Config, String) { )) |> clip.flag(flag.help( flag.new("local"), - "Cache data local in user's home directory for use only by this project", + "Cache data in the project's .go-over/ directory for use only by this project", )) |> clip.flag(flag.help( flag.new("verbose"), "Print progress as packages are checked", )) - |> clip.opt( - opt.new("format") - |> opt.help( - "Specify the output format of any warnings, [minimal, verbose, json]", - ) - |> opt.map(parse_config_format) - |> opt.default(option.None), + |> clip.help(help.custom(help_message)) +} + +pub fn parse_flags(argv: List(String)) -> Result(Flags, String) { + let argv = normalize_workspace_argv(argv) + let #(format, puller, single_root, workspace_root, sarif_output, argv) = + take_named_opts(argv) + + use base <- result.try( + clip_command() + |> clip.run(cli.strip_js_from_argv(argv)), ) - |> clip.opt( - opt.new("puller") - |> opt.help( - "Specify the tool used to reach out to hex.pm, [native, curl, wget, httpie]", - ) - |> opt.map(parse_puller) - |> opt.default(option.None), + + Ok( + Flags( + ..base, + format: option.map(format, parse_config_format) |> option.flatten, + puller: option.map(puller, parse_puller) |> option.flatten, + single_root:, + workspace_root:, + sarif_output:, + ), ) - |> clip.help(help.custom(help_message)) - |> clip.run(cli.strip_js_from_argv(argv)) - |> result.flatten +} + +pub fn spin_up(cfg: Config, argv: List(String)) -> Result(Config, String) { + use flags <- result.try(parse_flags(argv)) + merge_flags_and_config(flags, cfg) } diff --git a/src/go_over/hex/core.gleam b/src/go_over/hex/core.gleam index 5913fa2..691bd26 100644 --- a/src/go_over/hex/core.gleam +++ b/src/go_over/hex/core.gleam @@ -13,9 +13,9 @@ pub fn release_path(pkg: packages.Package) -> String { |> filepath.join(pkg.version_raw) } -pub fn hex_info_path(pkg: packages.Package) -> String { +pub fn package_licenses_path(pkg: packages.Package) -> String { globals.go_over_path() - |> filepath.join("hex-info") + |> filepath.join("package-licenses") |> filepath.join(pkg.name) |> filepath.join(pkg.version_raw) } @@ -33,10 +33,10 @@ pub fn release_filename(pkg) -> String { |> filepath.join("resp.json") } -pub fn hex_info_filename(pkg) -> String { +pub fn package_licenses_filename(pkg) -> String { pkg - |> hex_info_path() - |> filepath.join("hex-info-resp.json") + |> package_licenses_path() + |> filepath.join("licenses-resp.json") } pub fn print_ret(ret: ReleaseRetirement) -> String { diff --git a/src/go_over/hex/hex.gleam b/src/go_over/hex/hex.gleam index bccad9e..54ac537 100644 --- a/src/go_over/hex/hex.gleam +++ b/src/go_over/hex/hex.gleam @@ -1,9 +1,6 @@ import gleam/dynamic/decode import gleam/json import gleam/list -import gleam/option.{type Option} -import gleam/order -import gleamsver import go_over/hex/core import go_over/hex/puller import go_over/packages.{type Package} @@ -15,13 +12,9 @@ import gxyz/function as gfunction import gxyz/list as glist import simplifile -pub type HexInfo { - HexInfo(latest_stable_version: Option(String), licenses: List(String)) -} - -fn pull_hex_info(puller: puller.Puller, pkg: Package) -> Nil { - print.progress("Checking latest version: " <> pkg.name <> " From hex.pm") - let pkg_path = core.hex_info_path(pkg) +fn pull_package_licenses(puller: puller.Puller, pkg: Package) -> Nil { + print.progress("Fetching licenses: " <> pkg.name <> " from hex.pm") + let pkg_path = core.package_licenses_path(pkg) let pkg_path_fail = core.pkg_pull_error(pkg, pkg_path) let _ = simplifile.delete(pkg_path) @@ -31,39 +24,33 @@ fn pull_hex_info(puller: puller.Puller, pkg: Package) -> Nil { let resp = core.do_pull_hex(puller, pkg, core.package_url(pkg)) pkg - |> core.hex_info_filename() + |> core.package_licenses_filename() |> simplifile.write(resp) |> cli.hard_fail_with_msg(pkg_path_fail) } -pub fn decode_latest_stable_version_and_licenses( - data: String, -) -> Result(HexInfo, json.DecodeError) { +pub fn decode_licenses(data: String) -> Result(List(String), json.DecodeError) { let decoder = { - use latest_stable_version <- decode.field( - "latest_stable_version", - decode.optional(decode.string), - ) use licenses <- decode.subfield( ["meta", "licenses"], decode.list(decode.string), ) - decode.success(HexInfo(latest_stable_version:, licenses:)) + decode.success(licenses) } json.parse(data, decoder) } -fn pull(puller: puller.Puller, pkg: Package) { +pub fn fetch_licenses(puller: puller.Puller, pkg: Package) -> List(String) { pkg - |> core.hex_info_path() + |> core.package_licenses_path() |> cache.pull_if_not_cached( constants.hour, - gfunction.freeze2(pull_hex_info, puller, pkg), - pkg.name <> ": latest stable version", + gfunction.freeze2(pull_package_licenses, puller, pkg), + pkg.name <> ": package licenses", ) - let cached_file_name = core.hex_info_filename(pkg) + let cached_file_name = core.package_licenses_filename(pkg) let resp = cached_file_name @@ -71,51 +58,28 @@ fn pull(puller: puller.Puller, pkg: Package) { |> cli.hard_fail_with_msg("failed to read " <> cached_file_name) cli.hard_fail_with_msg( - decode_latest_stable_version_and_licenses(resp), + decode_licenses(resp), "failed to parse " <> cached_file_name, ) } -fn check_outdated( - latest_version: String, - pkg: Package, - cached_file_name: String, -) { - let latest_semver = - gleamsver.parse(latest_version) - |> cli.hard_fail_with_msg("failed to parse: " <> cached_file_name) - - case gleamsver.compare(latest_semver, pkg.version) { - order.Gt -> option.Some(latest_version) - _ -> option.None - } -} - pub type HexWarningSource { RejectedLicense(name: String) - Outdated(new_version: String) +} + +pub fn rejected_license_sources( + licenses: List(String), + allowed_licenses: List(String), +) -> List(HexWarningSource) { + glist.reject_contains(licenses, allowed_licenses) + |> list.map(RejectedLicense) } pub fn get_hex_info( puller: puller.Puller, pkg: Package, allowed_licenses: List(String), -) { - let info = pull(puller, pkg) - let cached_file_name = core.hex_info_filename(pkg) - - let outdated = - info.latest_stable_version - |> option.map(check_outdated(_, pkg, cached_file_name)) - |> option.flatten() - |> option.map(Outdated) - - let rejected_licenses = - glist.reject_contains(info.licenses, allowed_licenses) - |> list.map(RejectedLicense) - - case outdated { - option.None -> rejected_licenses - option.Some(outdated) -> [outdated, ..rejected_licenses] - } +) -> List(HexWarningSource) { + fetch_licenses(puller, pkg) + |> rejected_license_sources(allowed_licenses) } diff --git a/src/go_over/sarif.gleam b/src/go_over/sarif.gleam new file mode 100644 index 0000000..ee5b1f1 --- /dev/null +++ b/src/go_over/sarif.gleam @@ -0,0 +1,143 @@ +import filepath +import gleam/json.{type Json, int, object, preprocessed_array, string} +import gleam/list +import gleam/option +import go_over/util/constants +import go_over/warning.{ + type Severity, type Warning, SeverityCritical, SeverityHigh, SeverityInfo, + SeverityLow, SeverityModerate, SeverityPackageRetiredDeprecated, + SeverityPackageRetiredInvalid, SeverityPackageRetiredOtherReason, + SeverityPackageRetiredRenamed, SeverityPackageRetiredSecurity, + SeverityRejectedLicense, SeverityUnknown, WarningReasonInfo, + WarningReasonRejectedLicense, WarningReasonRetired, WarningReasonVulnerable, + severity_as_string, +} + +const schema = "https://json.schemastore.org/sarif-2.1.0.json" + +pub fn to_sarif_log(runs: List(#(String, List(Warning)))) -> Json { + object([ + #("$schema", string(schema)), + #("version", string("2.1.0")), + #( + "runs", + runs + |> list.map(fn(run) { + let #(project_root, warnings) = run + to_sarif_run(project_root, warnings) + }) + |> preprocessed_array(), + ), + ]) +} + +pub fn to_sarif_run(project_root: String, warnings: List(Warning)) -> Json { + let rules = build_rules(warnings) + let results = list.map(warnings, fn(w) { to_result(w, project_root) }) + + object([ + #( + "tool", + object([ + #( + "driver", + object([ + #("name", string("go_over")), + #("version", string(constants.version)), + #("rules", rules), + ]), + ), + ]), + ), + #("results", preprocessed_array(results)), + ]) +} + +fn build_rules(warnings: List(Warning)) -> Json { + warnings + |> list.map(rule_id) + |> list.unique() + |> list.map(fn(id) { + object([ + #("id", string(id)), + #("shortDescription", object([#("text", string(id))])), + ]) + }) + |> preprocessed_array() +} + +fn to_result(w: Warning, project_root: String) -> Json { + let manifest_uri = filepath.join(project_root, "manifest.toml") + + object([ + #("ruleId", string(rule_id(w))), + #("level", string(sarif_level(w.severity))), + #( + "message", + object([ + #( + "text", + string( + w.package + <> option.map(w.version, fn(v) { "@" <> v }) + |> option.unwrap("") + <> ": " + <> w.reason, + ), + ), + ]), + ), + #( + "locations", + preprocessed_array([ + object([ + #( + "physicalLocation", + object([ + #("artifactLocation", object([#("uri", string(manifest_uri))])), + #( + "region", + object([ + #("startLine", int(1)), + #("startColumn", int(1)), + ]), + ), + ]), + ), + ]), + ]), + ), + ]) +} + +fn rule_id(w: Warning) -> String { + case w.advisory_id { + option.Some(id) -> id + option.None -> + case w.warning_reason_code { + WarningReasonVulnerable -> "go-over/vulnerable" + WarningReasonRetired -> "go-over/retired" + WarningReasonRejectedLicense(_) -> "go-over/rejected-license" + WarningReasonInfo -> "go-over/info" + } + <> ":" + <> severity_as_string(w.severity) + } +} + +fn sarif_level(severity: Severity) -> String { + case severity { + SeverityCritical + | SeverityHigh + | SeverityPackageRetiredSecurity + | SeverityRejectedLicense -> "error" + SeverityModerate + | SeverityPackageRetiredRenamed + | SeverityPackageRetiredDeprecated -> "warning" + SeverityLow + | SeverityPackageRetiredInvalid + | SeverityPackageRetiredOtherReason(_) + | SeverityInfo + | SeverityUnknown(_) -> "note" + } +} diff --git a/src/go_over/sources.gleam b/src/go_over/sources.gleam index 4af3af6..17439ae 100644 --- a/src/go_over/sources.gleam +++ b/src/go_over/sources.gleam @@ -1,5 +1,5 @@ import gleam/list -import gleam/option.{None, Some} +import gleam/option import gleam/pair import go_over/advisories/advisories import go_over/config.{type Config} @@ -13,8 +13,9 @@ import gxyz/tuple pub fn get_vulnerable_warnings( pkgs: List(Package), conf: Config, + all_advisories: List(advisories.Advisory), ) -> List(Warning) { - advisories.check_for_advisories(pkgs) + advisories.check_for_advisories(pkgs, all_advisories) |> list.map(fn(p) { tuple.map2_1(p, config.filter_advisory_ids(conf, _)) }) |> glist.filter_tap(pair.second, list.is_empty) |> list.flat_map(tuple.apply_from2(_, warning.adv_to_warning)) @@ -33,25 +34,24 @@ pub fn get_retired_warnings( |> list.map(tuple.apply_from2(_, warning.retired_to_warning)) } -pub fn get_hex_warnings(pkgs: List(Package), conf: Config) -> List(Warning) { - let check_licenses = !list.is_empty(conf.allowed_licenses) - let outdated = conf.outdated +pub fn get_hex_warnings( + pkgs: List(Package), + conf: Config, +) -> #(List(Warning), List(String)) { let allowed_licenses = conf.allowed_licenses - list.flat_map(pkgs, fn(pkg) { - let sources = hex.get_hex_info(conf.puller, pkg, allowed_licenses) - - list.map(sources, fn(source) { - case source, outdated, check_licenses { - hex.Outdated(new_version), True, _ -> - Some(warning.outdated_to_warning(pkg, new_version)) + list.fold(pkgs, #([], []), fn(acc, pkg) { + let #(warnings, licenses) = acc + let pkg_licenses = hex.fetch_licenses(conf.puller, pkg) + let pkg_warnings = + hex.rejected_license_sources(pkg_licenses, allowed_licenses) + |> list.map(fn(source) { + case source { + hex.RejectedLicense(name) -> + warning.rejected_license_to_warning(pkg, name) + } + }) - hex.RejectedLicense(name), _, True -> - Some(warning.rejected_license_to_warning(pkg, name)) - - _, _, _ -> None - } - }) + #(list.append(warnings, pkg_warnings), list.append(licenses, pkg_licenses)) }) - |> option.values() } diff --git a/src/go_over/util/constants.gleam b/src/go_over/util/constants.gleam index ed08512..ae52571 100644 --- a/src/go_over/util/constants.gleam +++ b/src/go_over/util/constants.gleam @@ -2,7 +2,7 @@ pub const hour = 3600 pub const six_hours = 21_600 -pub const version = "3.3.0" +pub const version = "4.0.0" pub const advisories_repo = "mirego/elixir-security-advisories" diff --git a/src/go_over/util/globals.gleam b/src/go_over/util/globals.gleam index 6b7148f..72fe1f5 100644 --- a/src/go_over/util/globals.gleam +++ b/src/go_over/util/globals.gleam @@ -3,25 +3,28 @@ import filepath import global_value import gxyz/cli import gxyz/function -import simplifile + +const project_root_key = "project_root.global.data" + +pub fn set_project_root(root: String) -> String { + global_value.create_with_unique_name(project_root_key, function.freeze(root)) +} + +pub fn project_root() -> String { + global_value.create_with_unique_name(project_root_key, function.freeze(".")) +} pub fn go_over_path() -> String { - global_value.create_with_unique_name("go_over_path.global.data", fn() { - let #(path, name) = case use_global_cache() { - True -> #( - directories.cache_dir() - |> cli.hard_fail_with_msg("could not get cache directory"), - "go-over", - ) - False -> #( - simplifile.current_directory() - |> cli.hard_fail_with_msg("could not get current directory"), - ".go-over", - ) - } - - filepath.join(path, name) - }) + let #(path, name) = case use_global_cache() { + True -> #( + directories.cache_dir() + |> cli.hard_fail_with_msg("could not get cache directory"), + "go-over", + ) + False -> #(project_root(), ".go-over") + } + + filepath.join(path, name) } const verbose_key = "verbose.global.data" diff --git a/src/go_over/util/print.gleam b/src/go_over/util/print.gleam index b0929c8..c6af8d4 100644 --- a/src/go_over/util/print.gleam +++ b/src/go_over/util/print.gleam @@ -34,6 +34,10 @@ pub fn format_low(msg: String) { shellout.style(msg <> "\n", with: shellout.color(["cyan"]), custom: []) } +pub fn format_info(msg: String) { + shellout.style(msg <> "\n", with: shellout.color(["brightgreen"]), custom: []) +} + pub fn warning(msg: String) { msg |> format_warning() diff --git a/src/go_over/warning.gleam b/src/go_over/warning.gleam index 0a3d2cc..fc513a1 100644 --- a/src/go_over/warning.gleam +++ b/src/go_over/warning.gleam @@ -1,26 +1,26 @@ import gleam/hexpm.{type ReleaseRetirement} -import gleam/json.{type Json, object, string} +import gleam/json.{type Json, nullable, object, string} import gleam/list import gleam/option.{type Option, None, Some} import gleam/string import go_over/advisories/advisories.{type Advisory} import go_over/hex/core -import go_over/packages.{type Package} +import go_over/packages.{type Package, PackageSourceGit} import go_over/util/print pub type WarningReasonCode { WarningReasonRetired WarningReasonVulnerable - WarningReasonOutdated WarningReasonRejectedLicense(name: String) + WarningReasonInfo } fn warning_reason_code_as_string(w: WarningReasonCode) -> String { case w { WarningReasonRetired -> "Retired" WarningReasonVulnerable -> "Vulnerable" - WarningReasonOutdated -> "Outdated" WarningReasonRejectedLicense(name) -> "Rejected License (" <> name <> ")" + WarningReasonInfo -> "Info" } } @@ -30,12 +30,12 @@ pub type Severity { SeverityPackageRetiredDeprecated SeverityPackageRetiredRenamed SeverityPackageRetiredOtherReason(reason: String) - SeverityPackageOutdated SeverityRejectedLicense SeverityCritical SeverityHigh SeverityLow SeverityModerate + SeverityInfo SeverityUnknown(info: String) } @@ -47,12 +47,12 @@ pub fn severity_as_string(s: Severity) -> String { SeverityPackageRetiredRenamed -> "package-retired:renamed" SeverityPackageRetiredOtherReason(reason) -> "package-retired:" <> string.lowercase(reason) - SeverityPackageOutdated -> "package-outdated" SeverityRejectedLicense -> "rejected-license" SeverityCritical -> "critical" SeverityHigh -> "high" SeverityLow -> "low" SeverityModerate -> "moderate" + SeverityInfo -> "info" SeverityUnknown(value) -> string.join(["unknown", string.lowercase(value)], "-") } @@ -65,12 +65,12 @@ pub fn string_to_severity(s: String) -> Severity { "package-retired:deprecated" -> SeverityPackageRetiredDeprecated "package-retired:renamed" -> SeverityPackageRetiredRenamed "package-retired:" <> v -> SeverityPackageRetiredOtherReason(v) - "package-outdated" -> SeverityPackageOutdated "rejected-license" -> SeverityRejectedLicense "critical" -> SeverityCritical "high" -> SeverityHigh "low" -> SeverityLow "moderate" -> SeverityModerate + "info" -> SeverityInfo "unknown-" <> v -> SeverityUnknown(v) v -> SeverityUnknown(v) } @@ -145,30 +145,55 @@ pub fn retired_to_warning(pkg: Package, ret: ReleaseRetirement) -> Warning { ) } -pub fn outdated_to_warning(pkg: Package, new_version: String) -> Warning { +pub fn rejected_license_to_warning(pkg: Package, license: String) -> Warning { Warning( None, pkg.name, - Some(pkg.version_raw), - "New Version: '" <> new_version <> "' exists", - WarningReasonOutdated, - SeverityPackageOutdated, + None, + "Rejected License found: " <> license, + WarningReasonRejectedLicense(license), + SeverityRejectedLicense, dep_code_from_bool(pkg.direct), ) } -pub fn rejected_license_to_warning(pkg: Package, license: String) -> Warning { +pub fn info_to_warning(target: String, reason: String) -> Warning { Warning( None, - pkg.name, + target, None, - "Rejected License found: " <> license, - WarningReasonRejectedLicense(license), - SeverityRejectedLicense, - dep_code_from_bool(pkg.direct), + reason, + WarningReasonInfo, + SeverityInfo, + DirectDep, ) } +pub fn git_deps_to_warnings(pkgs: List(Package)) -> List(Warning) { + let git_names = + pkgs + |> list.filter(fn(p) { p.source == PackageSourceGit }) + |> list.map(fn(p) { p.name }) + + case git_names { + [] -> [] + names -> [ + info_to_warning( + "git-dependencies", + "Info: git dependencies have limited support (retirement and license checks are not performed): " + <> string.join(names, ", "), + ), + ] + } +} + +pub fn is_info(w: Warning) -> Bool { + case w.severity { + SeverityInfo -> True + _ -> False + } +} + pub fn format_as_string(w: Warning) -> String { [ "ID: " <> option.unwrap(w.advisory_id, "null"), @@ -197,9 +222,9 @@ pub fn format_as_string_minimal(w: Warning) -> String { pub fn format_as_json(w: Warning) -> Json { object([ - #("id", json.nullable(w.advisory_id, string)), + #("id", nullable(w.advisory_id, string)), #("package", string(w.package)), - #("version", json.nullable(w.version, string)), + #("version", nullable(w.version, string)), #( "warning_reason", string(warning_reason_code_as_string(w.warning_reason_code)), @@ -217,10 +242,11 @@ fn color(w: Warning, str: String) { SeverityHigh | SeverityRejectedLicense -> print.format_high(str) SeverityModerate | SeverityPackageRetiredRenamed - | SeverityPackageRetiredDeprecated - | SeverityPackageOutdated -> print.format_moderate(str) - SeverityLow | SeverityPackageRetiredInvalid -> print.format_low(str) - SeverityUnknown(_) | SeverityPackageRetiredOtherReason(_) -> - print.format_warning(str) + | SeverityPackageRetiredDeprecated -> print.format_moderate(str) + SeverityLow + | SeverityPackageRetiredInvalid + | SeverityPackageRetiredOtherReason(_) -> print.format_low(str) + SeverityInfo -> print.format_info(str) + SeverityUnknown(_) -> print.format_warning(str) } } diff --git a/src/go_over/workspace.gleam b/src/go_over/workspace.gleam new file mode 100644 index 0000000..3d3017b --- /dev/null +++ b/src/go_over/workspace.gleam @@ -0,0 +1,118 @@ +import filepath +import gleam/list +import gleam/order +import gleam/string +import simplifile + +const skip_dirs = ["build", "deps", "node_modules", ".go-over", ".git"] + +pub type DiscoverResult { + DiscoverResult(projects: List(String), skipped: List(String)) +} + +fn is_project_dir(dir: String) -> Bool { + let gleam_toml = filepath.join(dir, "gleam.toml") + let manifest = filepath.join(dir, "manifest.toml") + + case simplifile.is_file(gleam_toml), simplifile.is_file(manifest) { + Ok(True), Ok(True) -> True + _, _ -> False + } +} + +fn should_skip(name: String) -> Bool { + string.starts_with(name, ".") || list.contains(skip_dirs, name) +} + +fn find_projects_unlimited(dir: String) -> List(String) { + case is_project_dir(dir) { + True -> [dir] + False -> + case simplifile.read_directory(dir) { + Ok(names) -> + names + |> list.filter(fn(name) { !should_skip(name) }) + |> list.flat_map(fn(name) { + find_projects_unlimited(filepath.join(dir, name)) + }) + Error(_) -> [] + } + } +} + +fn has_project_descendant(dir: String) -> Bool { + !list.is_empty(find_projects_unlimited(dir)) +} + +fn do_discover( + dir: String, + depth: Int, + max_depth: Int, + skipped: List(String), +) -> #(List(String), List(String)) { + case is_project_dir(dir) { + True -> #([dir], skipped) + False -> + case depth >= max_depth { + True -> { + let skipped = case has_project_descendant(dir) { + True -> list.append(skipped, find_projects_unlimited(dir)) + False -> skipped + } + #([], skipped) + } + False -> + case simplifile.read_directory(dir) { + Ok(names) -> + names + |> list.filter(fn(name) { !should_skip(name) }) + |> list.fold(#([], skipped), fn(acc, name) { + let #(projects, skipped) = acc + let child = filepath.join(dir, name) + let #(child_projects, child_skipped) = + do_discover(child, depth + 1, max_depth, []) + #( + list.append(projects, child_projects), + list.append(skipped, child_skipped), + ) + }) + Error(_) -> #([], skipped) + } + } + } +} + +pub fn discover(scan_root: String, max_depth: Int) -> DiscoverResult { + let #(projects, skipped) = do_discover(scan_root, 0, max_depth, []) + + DiscoverResult( + projects: projects + |> list.sort(fn(a, b) { + case string.compare(a, b) { + order.Eq -> order.Eq + order.Lt -> order.Lt + order.Gt -> order.Gt + } + }), + skipped: skipped + |> list.unique() + |> list.sort(fn(a, b) { + case string.compare(a, b) { + order.Eq -> order.Eq + order.Lt -> order.Lt + order.Gt -> order.Gt + } + }), + ) +} + +pub fn discover_or_error( + scan_root: String, + max_depth: Int, +) -> Result(DiscoverResult, String) { + case discover(scan_root, max_depth) { + DiscoverResult([], _) -> + Error("no gleam projects found under " <> scan_root) + result -> Ok(result) + } +} diff --git a/test/advisories_test.gleam b/test/advisories_test.gleam index ebd6581..452718e 100644 --- a/test/advisories_test.gleam +++ b/test/advisories_test.gleam @@ -1,4 +1,4 @@ -import go_over/advisories/advisories.{check_for_advisories, read} +import go_over/advisories/advisories.{check_for_advisories, fetch_all, read} import go_over/packages.{read_manifest} import go_over_test import simplifile @@ -6,7 +6,7 @@ import simplifile pub fn check_for_advisories_test() { let assert [#(pkg, [adv1, adv2])] = read_manifest("test/testdata/manifest/known_vulnerable.toml") - |> check_for_advisories() + |> check_for_advisories(fetch_all()) assert pkg.name == "phoenix" assert adv1.name == "phoenix" diff --git a/birdie_snapshots/erlang@advisories_test@test_testdata_advisories_all_yaml.accepted b/test/birdie_snapshots/erlang@advisories_test@test_testdata_advisories_all_yaml.accepted similarity index 100% rename from birdie_snapshots/erlang@advisories_test@test_testdata_advisories_all_yaml.accepted rename to test/birdie_snapshots/erlang@advisories_test@test_testdata_advisories_all_yaml.accepted diff --git a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_basic_toml.accepted b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_basic_toml.accepted similarity index 82% rename from birdie_snapshots/erlang@conf_test@test_testdata_gleam_basic_toml.accepted rename to test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_basic_toml.accepted index 269c9f1..be98e34 100644 --- a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_basic_toml.accepted +++ b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_basic_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@conf_test@test/testdata/gleam/basic.toml --- Config( @@ -16,4 +16,7 @@ Config( ["b"], ["c"], False, -) \ No newline at end of file + 3, + None, + None, +) diff --git a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_empty_toml.accepted b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_empty_toml.accepted similarity index 82% rename from birdie_snapshots/erlang@conf_test@test_testdata_gleam_empty_toml.accepted rename to test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_empty_toml.accepted index 7cf4087..1e0c614 100644 --- a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_empty_toml.accepted +++ b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_empty_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@conf_test@test/testdata/gleam/empty.toml --- Config( @@ -16,4 +16,7 @@ Config( [], [], False, -) \ No newline at end of file + 3, + None, + None, +) diff --git a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_full_toml.accepted b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_full_toml.accepted similarity index 84% rename from birdie_snapshots/erlang@conf_test@test_testdata_gleam_full_toml.accepted rename to test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_full_toml.accepted index 3f1d049..df3a7f8 100644 --- a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_full_toml.accepted +++ b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_full_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@conf_test@test/testdata/gleam/full.toml --- Config( @@ -16,4 +16,7 @@ Config( ["critical", "high"], ["a", "b"], True, -) \ No newline at end of file + 3, + None, + None, +) diff --git a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_indirect_new_toml.accepted b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_indirect_new_toml.accepted similarity index 82% rename from birdie_snapshots/erlang@conf_test@test_testdata_gleam_indirect_new_toml.accepted rename to test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_indirect_new_toml.accepted index d908cd1..6970c50 100644 --- a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_indirect_new_toml.accepted +++ b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_indirect_new_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@conf_test@test/testdata/gleam/indirect_new.toml --- Config( @@ -16,4 +16,7 @@ Config( [], [], False, -) \ No newline at end of file + 3, + None, + None, +) diff --git a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_partial_toml.accepted b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_partial_toml.accepted similarity index 83% rename from birdie_snapshots/erlang@conf_test@test_testdata_gleam_partial_toml.accepted rename to test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_partial_toml.accepted index e9e5983..093d969 100644 --- a/birdie_snapshots/erlang@conf_test@test_testdata_gleam_partial_toml.accepted +++ b/test/birdie_snapshots/erlang@conf_test@test_testdata_gleam_partial_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@conf_test@test/testdata/gleam/partial.toml --- Config( @@ -16,4 +16,7 @@ Config( [], [], False, -) \ No newline at end of file + 3, + None, + None, +) diff --git a/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_empty_licenses_json.accepted b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_empty_licenses_json.accepted new file mode 100644 index 0000000..650e31f --- /dev/null +++ b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_empty_licenses_json.accepted @@ -0,0 +1,13 @@ +--- +version: 2.0.0 +title: Erlang@decode_licenses@test/testdata/hex/empty_licenses.json +--- +#( + "{ + "meta": { + "licenses": [] + } +} +", + Ok([]), +) diff --git a/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_full_json.accepted b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_full_json.accepted new file mode 100644 index 0000000..5cc800a --- /dev/null +++ b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_full_json.accepted @@ -0,0 +1,13 @@ +--- +version: 2.0.0 +title: Erlang@decode_licenses@test/testdata/hex/full.json +--- +#( + "{ + "meta": { + "licenses": ["MIT"] + } +} +", + Ok(["MIT"]), +) diff --git a/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_multi_license_json.accepted b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_multi_license_json.accepted new file mode 100644 index 0000000..4d7735c --- /dev/null +++ b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_multi_license_json.accepted @@ -0,0 +1,13 @@ +--- +version: 2.0.0 +title: Erlang@decode_licenses@test/testdata/hex/multi_license.json +--- +#( + "{ + "meta": { + "licenses": ["MIT", "Apache-2.0"] + } +} +", + Ok(["MIT", "Apache-2.0"]), +) diff --git a/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_license_json.accepted b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_license_json.accepted new file mode 100644 index 0000000..03be010 --- /dev/null +++ b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_license_json.accepted @@ -0,0 +1,17 @@ +--- +version: 2.0.0 +title: Erlang@decode_licenses@test/testdata/hex/no_license.json +--- +#( + "{ + "meta": {} +} +", + Error(UnableToDecode([ + DecodeError( + "Field", + "Nothing", + ["meta", "licenses"], + ), + ])), +) diff --git a/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_meta_json.accepted b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_meta_json.accepted new file mode 100644 index 0000000..77485b5 --- /dev/null +++ b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_meta_json.accepted @@ -0,0 +1,15 @@ +--- +version: 2.0.0 +title: Erlang@decode_licenses@test/testdata/hex/no_meta.json +--- +#( + "{} +", + Error(UnableToDecode([ + DecodeError( + "Field", + "Nothing", + ["meta"], + ), + ])), +) diff --git a/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_version_json.accepted b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_version_json.accepted new file mode 100644 index 0000000..5041407 --- /dev/null +++ b/test/birdie_snapshots/erlang@decode_licenses@test_testdata_hex_no_version_json.accepted @@ -0,0 +1,13 @@ +--- +version: 2.0.0 +title: Erlang@decode_licenses@test/testdata/hex/no_version.json +--- +#( + "{ + "meta": { + "licenses": ["bin"] + } +} +", + Ok(["bin"]), +) diff --git a/test/birdie_snapshots/erlang@integration@sarif_log_includes_info.accepted b/test/birdie_snapshots/erlang@integration@sarif_log_includes_info.accepted new file mode 100644 index 0000000..41640d2 --- /dev/null +++ b/test/birdie_snapshots/erlang@integration@sarif_log_includes_info.accepted @@ -0,0 +1,5 @@ +--- +version: 2.0.0 +title: Erlang@integration@sarif_log_includes_info +--- +"{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"go_over","version":"4.0.0","rules":[{"id":"go-over/info:info","shortDescription":{"text":"go-over/info:info"}},{"id":"GHSA-test","shortDescription":{"text":"GHSA-test"}}]}},"results":[{"ruleId":"go-over/info:info","level":"note","message":{"text":"missing-package: Info: package 'missing-package' is not a dependency"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"backend/manifest.toml"},"region":{"startLine":1,"startColumn":1}}}]},{"ruleId":"GHSA-test","level":"error","message":{"text":"pkg@1.0.0: example"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"backend/manifest.toml"},"region":{"startLine":1,"startColumn":1}}}]}]}]}" diff --git a/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_a_toml.accepted b/test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_a_toml.accepted similarity index 100% rename from birdie_snapshots/erlang@manifest_test@test_testdata_manifest_a_toml.accepted rename to test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_a_toml.accepted diff --git a/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_b_toml.accepted b/test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_b_toml.accepted similarity index 100% rename from birdie_snapshots/erlang@manifest_test@test_testdata_manifest_b_toml.accepted rename to test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_b_toml.accepted diff --git a/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_dos_toml.accepted b/test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_dos_toml.accepted similarity index 100% rename from birdie_snapshots/erlang@manifest_test@test_testdata_manifest_dos_toml.accepted rename to test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_dos_toml.accepted diff --git a/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_empty_toml.accepted b/test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_empty_toml.accepted similarity index 100% rename from birdie_snapshots/erlang@manifest_test@test_testdata_manifest_empty_toml.accepted rename to test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_empty_toml.accepted diff --git a/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_git_toml.accepted b/test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_git_toml.accepted similarity index 100% rename from birdie_snapshots/erlang@manifest_test@test_testdata_manifest_git_toml.accepted rename to test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_git_toml.accepted diff --git a/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_local_toml.accepted b/test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_local_toml.accepted similarity index 100% rename from birdie_snapshots/erlang@manifest_test@test_testdata_manifest_local_toml.accepted rename to test/birdie_snapshots/erlang@manifest_test@test_testdata_manifest_local_toml.accepted diff --git a/birdie_snapshots/erlang@spin_up_test@empty.accepted b/test/birdie_snapshots/erlang@spin_up_test@empty.accepted similarity index 81% rename from birdie_snapshots/erlang@spin_up_test@empty.accepted rename to test/birdie_snapshots/erlang@spin_up_test@empty.accepted index 5bd21c6..5f8e971 100644 --- a/birdie_snapshots/erlang@spin_up_test@empty.accepted +++ b/test/birdie_snapshots/erlang@spin_up_test@empty.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@spin_up_test@empty --- #( @@ -18,5 +18,8 @@ title: Erlang@spin_up_test@empty [], [], False, + 3, + None, + None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@spin_up_test@fake.accepted b/test/birdie_snapshots/erlang@spin_up_test@fake.accepted similarity index 100% rename from birdie_snapshots/erlang@spin_up_test@fake.accepted rename to test/birdie_snapshots/erlang@spin_up_test@fake.accepted diff --git a/birdie_snapshots/erlang@spin_up_test@force.accepted b/test/birdie_snapshots/erlang@spin_up_test@force.accepted similarity index 82% rename from birdie_snapshots/erlang@spin_up_test@force.accepted rename to test/birdie_snapshots/erlang@spin_up_test@force.accepted index 1c07609..73ad354 100644 --- a/birdie_snapshots/erlang@spin_up_test@force.accepted +++ b/test/birdie_snapshots/erlang@spin_up_test@force.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@spin_up_test@force --- #( @@ -18,5 +18,8 @@ title: Erlang@spin_up_test@force [], [], False, + 3, + None, + None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@spin_up_test@format=detailed.accepted b/test/birdie_snapshots/erlang@spin_up_test@format=detailed.accepted similarity index 83% rename from birdie_snapshots/erlang@spin_up_test@format=detailed.accepted rename to test/birdie_snapshots/erlang@spin_up_test@format=detailed.accepted index 1eb024e..b18f01d 100644 --- a/birdie_snapshots/erlang@spin_up_test@format=detailed.accepted +++ b/test/birdie_snapshots/erlang@spin_up_test@format=detailed.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@spin_up_test@format=detailed --- #( @@ -18,5 +18,8 @@ title: Erlang@spin_up_test@format=detailed [], [], False, + 3, + None, + None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@spin_up_test@format=json.accepted b/test/birdie_snapshots/erlang@spin_up_test@format=json.accepted similarity index 83% rename from birdie_snapshots/erlang@spin_up_test@format=json.accepted rename to test/birdie_snapshots/erlang@spin_up_test@format=json.accepted index 377ba09..5cc24a1 100644 --- a/birdie_snapshots/erlang@spin_up_test@format=json.accepted +++ b/test/birdie_snapshots/erlang@spin_up_test@format=json.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@spin_up_test@format=json --- #( @@ -18,5 +18,8 @@ title: Erlang@spin_up_test@format=json [], [], False, + 3, + None, + None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@spin_up_test@format=minimal.accepted b/test/birdie_snapshots/erlang@spin_up_test@format=minimal.accepted similarity index 83% rename from birdie_snapshots/erlang@spin_up_test@format=minimal.accepted rename to test/birdie_snapshots/erlang@spin_up_test@format=minimal.accepted index b5a5bc5..e6d6015 100644 --- a/birdie_snapshots/erlang@spin_up_test@format=minimal.accepted +++ b/test/birdie_snapshots/erlang@spin_up_test@format=minimal.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@spin_up_test@format=minimal --- #( @@ -18,5 +18,8 @@ title: Erlang@spin_up_test@format=minimal [], [], False, + 3, + None, + None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@spin_up_test@verbose.accepted b/test/birdie_snapshots/erlang@spin_up_test@verbose.accepted similarity index 82% rename from birdie_snapshots/erlang@spin_up_test@verbose.accepted rename to test/birdie_snapshots/erlang@spin_up_test@verbose.accepted index 11a604f..687afa5 100644 --- a/birdie_snapshots/erlang@spin_up_test@verbose.accepted +++ b/test/birdie_snapshots/erlang@spin_up_test@verbose.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@spin_up_test@verbose --- #( @@ -18,5 +18,8 @@ title: Erlang@spin_up_test@verbose [], [], False, + 3, + None, + None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@warning@adv_to_warning.accepted b/test/birdie_snapshots/erlang@warning@adv_to_warning.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@adv_to_warning.accepted rename to test/birdie_snapshots/erlang@warning@adv_to_warning.accepted diff --git a/test/birdie_snapshots/erlang@warning@git_deps_to_warnings.accepted b/test/birdie_snapshots/erlang@warning@git_deps_to_warnings.accepted new file mode 100644 index 0000000..f306945 --- /dev/null +++ b/test/birdie_snapshots/erlang@warning@git_deps_to_warnings.accepted @@ -0,0 +1,16 @@ +--- +version: 2.0.0 +title: Erlang@warning@git_deps_to_warnings +--- +#( + "c", + Warning( + None, + "git-dependencies", + None, + "Info: git dependencies have limited support (retirement and license checks are not performed): c", + WarningReasonInfo, + SeverityInfo, + DirectDep, + ), +) diff --git a/test/birdie_snapshots/erlang@warning@info_to_warning.accepted b/test/birdie_snapshots/erlang@warning@info_to_warning.accepted new file mode 100644 index 0000000..4217b3e --- /dev/null +++ b/test/birdie_snapshots/erlang@warning@info_to_warning.accepted @@ -0,0 +1,16 @@ +--- +version: 2.0.0 +title: Erlang@warning@info_to_warning +--- +#( + "missing-package", + Warning( + None, + "missing-package", + None, + "Info: package 'missing-package' is not a dependency", + WarningReasonInfo, + SeverityInfo, + DirectDep, + ), +) diff --git a/birdie_snapshots/erlang@warning@rejected_license_to_warning.accepted b/test/birdie_snapshots/erlang@warning@rejected_license_to_warning.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@rejected_license_to_warning.accepted rename to test/birdie_snapshots/erlang@warning@rejected_license_to_warning.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_deprecated.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_deprecated.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_deprecated.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_deprecated.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_deprecated_none.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_deprecated_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_deprecated_none.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_deprecated_none.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_invalid.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_invalid.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_invalid.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_invalid.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_invalid_none.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_invalid_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_invalid_none.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_invalid_none.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_other.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_other.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_other.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_other.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_other_none.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_other_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_other_none.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_other_none.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_renamed.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_renamed.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_renamed.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_renamed.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_renamed_none.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_renamed_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_renamed_none.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_renamed_none.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_security.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_security.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_security.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_security.accepted diff --git a/birdie_snapshots/erlang@warning@retired_to_warning_security_none.accepted b/test/birdie_snapshots/erlang@warning@retired_to_warning_security_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning@retired_to_warning_security_none.accepted rename to test/birdie_snapshots/erlang@warning@retired_to_warning_security_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@adv_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@adv_to_warning.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@adv_to_warning.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@adv_to_warning.accepted diff --git a/test/birdie_snapshots/erlang@warning_format_as_json@git_deps_to_warnings.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@git_deps_to_warnings.accepted new file mode 100644 index 0000000..4220907 --- /dev/null +++ b/test/birdie_snapshots/erlang@warning_format_as_json@git_deps_to_warnings.accepted @@ -0,0 +1,18 @@ +--- +version: 2.0.0 +title: Erlang@warning_format_as_json@git_deps_to_warnings +--- +#( + "c", + " +{ +"id": null, +"package": "git-dependencies", +"version": null, +"warning_reason": "Info", +"dependency_type": "Direct", +"severity": "info", +"reason": "Info: git dependencies have limited support (retirement and license checks are not performed): c" +} +", +) diff --git a/test/birdie_snapshots/erlang@warning_format_as_json@info_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@info_to_warning.accepted new file mode 100644 index 0000000..f8d52b1 --- /dev/null +++ b/test/birdie_snapshots/erlang@warning_format_as_json@info_to_warning.accepted @@ -0,0 +1,18 @@ +--- +version: 2.0.0 +title: Erlang@warning_format_as_json@info_to_warning +--- +#( + "missing-package", + " +{ +"id": null, +"package": "missing-package", +"version": null, +"warning_reason": "Info", +"dependency_type": "Direct", +"severity": "info", +"reason": "Info: package 'missing-package' is not a dependency" +} +", +) diff --git a/birdie_snapshots/erlang@warning_format_as_json@rejected_license_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@rejected_license_to_warning.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@rejected_license_to_warning.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@rejected_license_to_warning.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_deprecated.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_deprecated.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_deprecated.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_deprecated.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_deprecated_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_deprecated_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_deprecated_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_deprecated_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_invalid.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_invalid.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_invalid.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_invalid.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_invalid_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_invalid_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_invalid_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_invalid_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_other.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_other.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_other.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_other.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_other_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_other_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_other_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_other_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_renamed.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_renamed.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_renamed.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_renamed.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_renamed_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_renamed_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_renamed_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_renamed_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_security.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_security.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_security.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_security.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_security_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_security_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_security_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_json@retired_to_warning_security_none.accepted diff --git a/test/birdie_snapshots/erlang@warning_format_as_sarif@adv_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_sarif@adv_to_warning.accepted new file mode 100644 index 0000000..c25d2aa --- /dev/null +++ b/test/birdie_snapshots/erlang@warning_format_as_sarif@adv_to_warning.accepted @@ -0,0 +1,81 @@ +--- +version: 2.0.0 +title: Erlang@warning_format_as_sarif@adv_to_warning +--- +#( + Warning( + Some("GHSA-test-1234"), + "package for warning tests", + Some("pre1.2.3-build"), + "example vulnerability", + WarningReasonVulnerable, + SeverityHigh, + DirectDep, + ), + " +{ +"tool": +{ +"driver": +{ +"name": "go_over", +"version": "4.0.0", +"rules": +[ + +{ +"id": "GHSA-test-1234", +"shortDescription": +{ +"text": "GHSA-test-1234" +} + +} + +] + +} + +} +, +"results": +[ + +{ +"ruleId": "GHSA-test-1234", +"level": "error", +"message": +{ +"text": "package for warning tests@pre1.2.3-build: example vulnerability" +} +, +"locations": +[ + +{ +"physicalLocation": +{ +"artifactLocation": +{ +"uri": "backend/manifest.toml" +} +, +"region": +{ +"startLine": 1, +"startColumn": 1 +} + +} + +} + +] + +} + +] + +} +", +) diff --git a/test/birdie_snapshots/erlang@warning_format_as_sarif@info_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_sarif@info_to_warning.accepted new file mode 100644 index 0000000..03c95e1 --- /dev/null +++ b/test/birdie_snapshots/erlang@warning_format_as_sarif@info_to_warning.accepted @@ -0,0 +1,81 @@ +--- +version: 2.0.0 +title: Erlang@warning_format_as_sarif@info_to_warning +--- +#( + Warning( + None, + "missing-package", + None, + "Info: package 'missing-package' is not a dependency", + WarningReasonInfo, + SeverityInfo, + DirectDep, + ), + " +{ +"tool": +{ +"driver": +{ +"name": "go_over", +"version": "4.0.0", +"rules": +[ + +{ +"id": "go-over/info:info", +"shortDescription": +{ +"text": "go-over/info:info" +} + +} + +] + +} + +} +, +"results": +[ + +{ +"ruleId": "go-over/info:info", +"level": "note", +"message": +{ +"text": "missing-package: Info: package 'missing-package' is not a dependency" +} +, +"locations": +[ + +{ +"physicalLocation": +{ +"artifactLocation": +{ +"uri": "backend/manifest.toml" +} +, +"region": +{ +"startLine": 1, +"startColumn": 1 +} + +} + +} + +] + +} + +] + +} +", +) diff --git a/birdie_snapshots/erlang@warning_format_as_string@adv_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@adv_to_warning.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@adv_to_warning.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@adv_to_warning.accepted diff --git a/test/birdie_snapshots/erlang@warning_format_as_string@git_deps_to_warnings.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@git_deps_to_warnings.accepted new file mode 100644 index 0000000..08bcbc9 --- /dev/null +++ b/test/birdie_snapshots/erlang@warning_format_as_string@git_deps_to_warnings.accepted @@ -0,0 +1,15 @@ +--- +version: 2.0.0 +title: Erlang@warning_format_as_string@git_deps_to_warnings +--- +#( + "c", + "ID: null +Package: git-dependencies +Version: null +WarningReason: Info +Dependency Type: Direct +Severity: info +Reason: Info: git dependencies have limited support (retirement and license checks are not performed): c +", +) diff --git a/test/birdie_snapshots/erlang@warning_format_as_string@info_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@info_to_warning.accepted new file mode 100644 index 0000000..f35fa20 --- /dev/null +++ b/test/birdie_snapshots/erlang@warning_format_as_string@info_to_warning.accepted @@ -0,0 +1,15 @@ +--- +version: 2.0.0 +title: Erlang@warning_format_as_string@info_to_warning +--- +#( + "missing-package", + "ID: null +Package: missing-package +Version: null +WarningReason: Info +Dependency Type: Direct +Severity: info +Reason: Info: package 'missing-package' is not a dependency +", +) diff --git a/birdie_snapshots/erlang@warning_format_as_string@rejected_license_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@rejected_license_to_warning.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@rejected_license_to_warning.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@rejected_license_to_warning.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_deprecated.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_deprecated.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_deprecated.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_deprecated.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_deprecated_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_deprecated_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_deprecated_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_deprecated_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_invalid.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_invalid.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_invalid.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_invalid.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_invalid_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_invalid_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_invalid_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_invalid_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other.accepted similarity index 93% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other.accepted index 3d66d4c..5567f1e 100644 --- a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other.accepted +++ b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@warning_format_as_string@retired_to_warning_other --- #( @@ -16,7 +16,7 @@ title: Erlang@warning_format_as_string@retired_to_warning_other Some("It's an example man"), ), ), - "ID: null + "ID: null Package: package for warning tests Version: pre1.2.3-build WarningReason: Retired @@ -24,4 +24,4 @@ Dependency Type: Direct Severity: package-retired:it's an example man Reason: other: It's an example man ", -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other_none.accepted similarity index 92% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other_none.accepted index 2f467d3..396fc8a 100644 --- a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other_none.accepted +++ b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_other_none.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@warning_format_as_string@retired_to_warning_other_none --- #( @@ -13,7 +13,7 @@ title: Erlang@warning_format_as_string@retired_to_warning_other_none ), ReleaseRetirement(OtherReason, None), ), - "ID: null + "ID: null Package: package for warning tests Version: pre1.2.3-build WarningReason: Retired @@ -21,4 +21,4 @@ Dependency Type: Direct Severity: package-retired:unknown Reason: other ", -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_renamed.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_renamed.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_renamed.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_renamed.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_renamed_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_renamed_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_renamed_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_renamed_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_security.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_security.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_security.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_security.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_security_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_security_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_security_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string@retired_to_warning_security_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@adv_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@adv_to_warning.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@adv_to_warning.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@adv_to_warning.accepted diff --git a/test/birdie_snapshots/erlang@warning_format_as_string_minimal@git_deps_to_warnings.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@git_deps_to_warnings.accepted new file mode 100644 index 0000000..6bfc302 --- /dev/null +++ b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@git_deps_to_warnings.accepted @@ -0,0 +1,9 @@ +--- +version: 2.0.0 +title: Erlang@warning_format_as_string_minimal@git_deps_to_warnings +--- +#( + "c", + "git-dependencies: info +", +) diff --git a/test/birdie_snapshots/erlang@warning_format_as_string_minimal@info_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@info_to_warning.accepted new file mode 100644 index 0000000..15256dc --- /dev/null +++ b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@info_to_warning.accepted @@ -0,0 +1,9 @@ +--- +version: 2.0.0 +title: Erlang@warning_format_as_string_minimal@info_to_warning +--- +#( + "missing-package", + "missing-package: info +", +) diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@rejected_license_to_warning.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@rejected_license_to_warning.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@rejected_license_to_warning.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@rejected_license_to_warning.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_deprecated.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_deprecated.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_deprecated.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_deprecated.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_deprecated_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_deprecated_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_deprecated_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_deprecated_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_invalid.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_invalid.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_invalid.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_invalid.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_invalid_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_invalid_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_invalid_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_invalid_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other.accepted similarity index 81% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other.accepted index 4c42ca3..6202820 100644 --- a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other.accepted +++ b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@warning_format_as_string_minimal@retired_to_warning_other --- #( @@ -16,6 +16,6 @@ title: Erlang@warning_format_as_string_minimal@retired_to_warning_other Some("It's an example man"), ), ), - "package for warning tests-pre1.2.3-build: package-retired:it's an example man + "package for warning tests-pre1.2.3-build: package-retired:it's an example man ", -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other_none.accepted similarity index 79% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other_none.accepted index 448fd7b..28b7d1a 100644 --- a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other_none.accepted +++ b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_other_none.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Erlang@warning_format_as_string_minimal@retired_to_warning_other_none --- #( @@ -13,6 +13,6 @@ title: Erlang@warning_format_as_string_minimal@retired_to_warning_other_none ), ReleaseRetirement(OtherReason, None), ), - "package for warning tests-pre1.2.3-build: package-retired:unknown + "package for warning tests-pre1.2.3-build: package-retired:unknown ", -) \ No newline at end of file +) diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_renamed.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_renamed.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_renamed.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_renamed.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_renamed_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_renamed_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_renamed_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_renamed_none.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_security.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_security.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_security.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_security.accepted diff --git a/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_security_none.accepted b/test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_security_none.accepted similarity index 100% rename from birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_security_none.accepted rename to test/birdie_snapshots/erlang@warning_format_as_string_minimal@retired_to_warning_security_none.accepted diff --git a/birdie_snapshots/javascript@advisories_test@test_testdata_advisories_all_yaml.accepted b/test/birdie_snapshots/javascript@advisories_test@test_testdata_advisories_all_yaml.accepted similarity index 100% rename from birdie_snapshots/javascript@advisories_test@test_testdata_advisories_all_yaml.accepted rename to test/birdie_snapshots/javascript@advisories_test@test_testdata_advisories_all_yaml.accepted diff --git a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_basic_toml.accepted b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_basic_toml.accepted similarity index 80% rename from birdie_snapshots/javascript@conf_test@test_testdata_gleam_basic_toml.accepted rename to test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_basic_toml.accepted index 33d1fba..8173b3b 100644 --- a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_basic_toml.accepted +++ b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_basic_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@conf_test@test/testdata/gleam/basic.toml --- Config( @@ -16,4 +16,7 @@ Config( ignore_severity: ["b"], ignore_ids: ["c"], ignore_dev_dependencies: False, -) \ No newline at end of file + workspace_max_depth: 3, + single_root: None, + workspace_root: None, +) diff --git a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_empty_toml.accepted b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_empty_toml.accepted similarity index 79% rename from birdie_snapshots/javascript@conf_test@test_testdata_gleam_empty_toml.accepted rename to test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_empty_toml.accepted index b7dba75..4c7ae89 100644 --- a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_empty_toml.accepted +++ b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_empty_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@conf_test@test/testdata/gleam/empty.toml --- Config( @@ -16,4 +16,7 @@ Config( ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, -) \ No newline at end of file + workspace_max_depth: 3, + single_root: None, + workspace_root: None, +) diff --git a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_full_toml.accepted b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_full_toml.accepted similarity index 81% rename from birdie_snapshots/javascript@conf_test@test_testdata_gleam_full_toml.accepted rename to test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_full_toml.accepted index edcc03f..b4a09bb 100644 --- a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_full_toml.accepted +++ b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_full_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@conf_test@test/testdata/gleam/full.toml --- Config( @@ -16,4 +16,7 @@ Config( ignore_severity: ["critical", "high"], ignore_ids: ["a", "b"], ignore_dev_dependencies: True, -) \ No newline at end of file + workspace_max_depth: 3, + single_root: None, + workspace_root: None, +) diff --git a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_indirect_new_toml.accepted b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_indirect_new_toml.accepted similarity index 79% rename from birdie_snapshots/javascript@conf_test@test_testdata_gleam_indirect_new_toml.accepted rename to test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_indirect_new_toml.accepted index 5237fd5..02dec0e 100644 --- a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_indirect_new_toml.accepted +++ b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_indirect_new_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@conf_test@test/testdata/gleam/indirect_new.toml --- Config( @@ -16,4 +16,7 @@ Config( ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, -) \ No newline at end of file + workspace_max_depth: 3, + single_root: None, + workspace_root: None, +) diff --git a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_partial_toml.accepted b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_partial_toml.accepted similarity index 80% rename from birdie_snapshots/javascript@conf_test@test_testdata_gleam_partial_toml.accepted rename to test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_partial_toml.accepted index 2bf1c14..708baf6 100644 --- a/birdie_snapshots/javascript@conf_test@test_testdata_gleam_partial_toml.accepted +++ b/test/birdie_snapshots/javascript@conf_test@test_testdata_gleam_partial_toml.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@conf_test@test/testdata/gleam/partial.toml --- Config( @@ -16,4 +16,7 @@ Config( ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, -) \ No newline at end of file + workspace_max_depth: 3, + single_root: None, + workspace_root: None, +) diff --git a/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_empty_licenses_json.accepted b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_empty_licenses_json.accepted new file mode 100644 index 0000000..0fbdae1 --- /dev/null +++ b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_empty_licenses_json.accepted @@ -0,0 +1,13 @@ +--- +version: 2.0.0 +title: Javascript@decode_licenses@test/testdata/hex/empty_licenses.json +--- +#( + "{ + "meta": { + "licenses": [] + } +} +", + Ok([]), +) diff --git a/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_full_json.accepted b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_full_json.accepted new file mode 100644 index 0000000..076c15e --- /dev/null +++ b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_full_json.accepted @@ -0,0 +1,13 @@ +--- +version: 2.0.0 +title: Javascript@decode_licenses@test/testdata/hex/full.json +--- +#( + "{ + "meta": { + "licenses": ["MIT"] + } +} +", + Ok(["MIT"]), +) diff --git a/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_multi_license_json.accepted b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_multi_license_json.accepted new file mode 100644 index 0000000..bd46ebe --- /dev/null +++ b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_multi_license_json.accepted @@ -0,0 +1,13 @@ +--- +version: 2.0.0 +title: Javascript@decode_licenses@test/testdata/hex/multi_license.json +--- +#( + "{ + "meta": { + "licenses": ["MIT", "Apache-2.0"] + } +} +", + Ok(["MIT", "Apache-2.0"]), +) diff --git a/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_license_json.accepted b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_license_json.accepted new file mode 100644 index 0000000..5a9579c --- /dev/null +++ b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_license_json.accepted @@ -0,0 +1,17 @@ +--- +version: 2.0.0 +title: Javascript@decode_licenses@test/testdata/hex/no_license.json +--- +#( + "{ + "meta": {} +} +", + Error(UnableToDecode([ + DecodeError( + expected: "Field", + found: "Nothing", + path: ["meta", "licenses"], + ), + ])), +) diff --git a/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_meta_json.accepted b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_meta_json.accepted new file mode 100644 index 0000000..26593d8 --- /dev/null +++ b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_meta_json.accepted @@ -0,0 +1,15 @@ +--- +version: 2.0.0 +title: Javascript@decode_licenses@test/testdata/hex/no_meta.json +--- +#( + "{} +", + Error(UnableToDecode([ + DecodeError( + expected: "Field", + found: "Nothing", + path: ["meta"], + ), + ])), +) diff --git a/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_version_json.accepted b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_version_json.accepted new file mode 100644 index 0000000..efb29a7 --- /dev/null +++ b/test/birdie_snapshots/javascript@decode_licenses@test_testdata_hex_no_version_json.accepted @@ -0,0 +1,13 @@ +--- +version: 2.0.0 +title: Javascript@decode_licenses@test/testdata/hex/no_version.json +--- +#( + "{ + "meta": { + "licenses": ["bin"] + } +} +", + Ok(["bin"]), +) diff --git a/test/birdie_snapshots/javascript@integration@sarif_log_includes_info.accepted b/test/birdie_snapshots/javascript@integration@sarif_log_includes_info.accepted new file mode 100644 index 0000000..79c2a68 --- /dev/null +++ b/test/birdie_snapshots/javascript@integration@sarif_log_includes_info.accepted @@ -0,0 +1,5 @@ +--- +version: 2.0.0 +title: Javascript@integration@sarif_log_includes_info +--- +"{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"go_over","version":"4.0.0","rules":[{"id":"go-over/info:info","shortDescription":{"text":"go-over/info:info"}},{"id":"GHSA-test","shortDescription":{"text":"GHSA-test"}}]}},"results":[{"ruleId":"go-over/info:info","level":"note","message":{"text":"missing-package: Info: package 'missing-package' is not a dependency"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"backend/manifest.toml"},"region":{"startLine":1,"startColumn":1}}}]},{"ruleId":"GHSA-test","level":"error","message":{"text":"pkg@1.0.0: example"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"backend/manifest.toml"},"region":{"startLine":1,"startColumn":1}}}]}]}]}" diff --git a/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_a_toml.accepted b/test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_a_toml.accepted similarity index 100% rename from birdie_snapshots/javascript@manifest_test@test_testdata_manifest_a_toml.accepted rename to test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_a_toml.accepted diff --git a/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_b_toml.accepted b/test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_b_toml.accepted similarity index 100% rename from birdie_snapshots/javascript@manifest_test@test_testdata_manifest_b_toml.accepted rename to test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_b_toml.accepted diff --git a/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_dos_toml.accepted b/test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_dos_toml.accepted similarity index 100% rename from birdie_snapshots/javascript@manifest_test@test_testdata_manifest_dos_toml.accepted rename to test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_dos_toml.accepted diff --git a/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_empty_toml.accepted b/test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_empty_toml.accepted similarity index 100% rename from birdie_snapshots/javascript@manifest_test@test_testdata_manifest_empty_toml.accepted rename to test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_empty_toml.accepted diff --git a/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_git_toml.accepted b/test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_git_toml.accepted similarity index 100% rename from birdie_snapshots/javascript@manifest_test@test_testdata_manifest_git_toml.accepted rename to test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_git_toml.accepted diff --git a/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_local_toml.accepted b/test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_local_toml.accepted similarity index 100% rename from birdie_snapshots/javascript@manifest_test@test_testdata_manifest_local_toml.accepted rename to test/birdie_snapshots/javascript@manifest_test@test_testdata_manifest_local_toml.accepted diff --git a/birdie_snapshots/javascript@spin_up_test@empty.accepted b/test/birdie_snapshots/javascript@spin_up_test@empty.accepted similarity index 79% rename from birdie_snapshots/javascript@spin_up_test@empty.accepted rename to test/birdie_snapshots/javascript@spin_up_test@empty.accepted index 7b47107..ce9b08e 100644 --- a/birdie_snapshots/javascript@spin_up_test@empty.accepted +++ b/test/birdie_snapshots/javascript@spin_up_test@empty.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@spin_up_test@empty --- #( @@ -18,5 +18,8 @@ title: Javascript@spin_up_test@empty ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, + workspace_max_depth: 3, + single_root: None, + workspace_root: None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@spin_up_test@fake.accepted b/test/birdie_snapshots/javascript@spin_up_test@fake.accepted similarity index 94% rename from birdie_snapshots/javascript@spin_up_test@fake.accepted rename to test/birdie_snapshots/javascript@spin_up_test@fake.accepted index 3dc1c6b..7015df3 100644 --- a/birdie_snapshots/javascript@spin_up_test@fake.accepted +++ b/test/birdie_snapshots/javascript@spin_up_test@fake.accepted @@ -6,7 +6,6 @@ title: Javascript@spin_up_test@fake ["--fake"], Config( dev_deps: [], - outdated: False, ignore_indirect: False, force: False, fake: True, diff --git a/birdie_snapshots/javascript@spin_up_test@force.accepted b/test/birdie_snapshots/javascript@spin_up_test@force.accepted similarity index 79% rename from birdie_snapshots/javascript@spin_up_test@force.accepted rename to test/birdie_snapshots/javascript@spin_up_test@force.accepted index 873f3f7..fa5224d 100644 --- a/birdie_snapshots/javascript@spin_up_test@force.accepted +++ b/test/birdie_snapshots/javascript@spin_up_test@force.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@spin_up_test@force --- #( @@ -18,5 +18,8 @@ title: Javascript@spin_up_test@force ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, + workspace_max_depth: 3, + single_root: None, + workspace_root: None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@spin_up_test@format=detailed.accepted b/test/birdie_snapshots/javascript@spin_up_test@format=detailed.accepted similarity index 80% rename from birdie_snapshots/javascript@spin_up_test@format=detailed.accepted rename to test/birdie_snapshots/javascript@spin_up_test@format=detailed.accepted index f84da40..78f7c0e 100644 --- a/birdie_snapshots/javascript@spin_up_test@format=detailed.accepted +++ b/test/birdie_snapshots/javascript@spin_up_test@format=detailed.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@spin_up_test@format=detailed --- #( @@ -18,5 +18,8 @@ title: Javascript@spin_up_test@format=detailed ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, + workspace_max_depth: 3, + single_root: None, + workspace_root: None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@spin_up_test@format=json.accepted b/test/birdie_snapshots/javascript@spin_up_test@format=json.accepted similarity index 80% rename from birdie_snapshots/javascript@spin_up_test@format=json.accepted rename to test/birdie_snapshots/javascript@spin_up_test@format=json.accepted index 92ddb33..e001a16 100644 --- a/birdie_snapshots/javascript@spin_up_test@format=json.accepted +++ b/test/birdie_snapshots/javascript@spin_up_test@format=json.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@spin_up_test@format=json --- #( @@ -18,5 +18,8 @@ title: Javascript@spin_up_test@format=json ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, + workspace_max_depth: 3, + single_root: None, + workspace_root: None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@spin_up_test@format=minimal.accepted b/test/birdie_snapshots/javascript@spin_up_test@format=minimal.accepted similarity index 80% rename from birdie_snapshots/javascript@spin_up_test@format=minimal.accepted rename to test/birdie_snapshots/javascript@spin_up_test@format=minimal.accepted index 747a25d..6c2044f 100644 --- a/birdie_snapshots/javascript@spin_up_test@format=minimal.accepted +++ b/test/birdie_snapshots/javascript@spin_up_test@format=minimal.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@spin_up_test@format=minimal --- #( @@ -18,5 +18,8 @@ title: Javascript@spin_up_test@format=minimal ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, + workspace_max_depth: 3, + single_root: None, + workspace_root: None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@spin_up_test@verbose.accepted b/test/birdie_snapshots/javascript@spin_up_test@verbose.accepted similarity index 79% rename from birdie_snapshots/javascript@spin_up_test@verbose.accepted rename to test/birdie_snapshots/javascript@spin_up_test@verbose.accepted index 866dda3..93f1b33 100644 --- a/birdie_snapshots/javascript@spin_up_test@verbose.accepted +++ b/test/birdie_snapshots/javascript@spin_up_test@verbose.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@spin_up_test@verbose --- #( @@ -18,5 +18,8 @@ title: Javascript@spin_up_test@verbose ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, + workspace_max_depth: 3, + single_root: None, + workspace_root: None, ), -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@warning@adv_to_warning.accepted b/test/birdie_snapshots/javascript@warning@adv_to_warning.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@adv_to_warning.accepted rename to test/birdie_snapshots/javascript@warning@adv_to_warning.accepted diff --git a/test/birdie_snapshots/javascript@warning@git_deps_to_warnings.accepted b/test/birdie_snapshots/javascript@warning@git_deps_to_warnings.accepted new file mode 100644 index 0000000..fad8000 --- /dev/null +++ b/test/birdie_snapshots/javascript@warning@git_deps_to_warnings.accepted @@ -0,0 +1,16 @@ +--- +version: 2.0.0 +title: Javascript@warning@git_deps_to_warnings +--- +#( + "c", + Warning( + advisory_id: None, + package: "git-dependencies", + version: None, + reason: "Info: git dependencies have limited support (retirement and license checks are not performed): c", + warning_reason_code: WarningReasonInfo, + severity: SeverityInfo, + dep: DirectDep, + ), +) diff --git a/test/birdie_snapshots/javascript@warning@info_to_warning.accepted b/test/birdie_snapshots/javascript@warning@info_to_warning.accepted new file mode 100644 index 0000000..db3da2f --- /dev/null +++ b/test/birdie_snapshots/javascript@warning@info_to_warning.accepted @@ -0,0 +1,16 @@ +--- +version: 2.0.0 +title: Javascript@warning@info_to_warning +--- +#( + "missing-package", + Warning( + advisory_id: None, + package: "missing-package", + version: None, + reason: "Info: package 'missing-package' is not a dependency", + warning_reason_code: WarningReasonInfo, + severity: SeverityInfo, + dep: DirectDep, + ), +) diff --git a/birdie_snapshots/javascript@warning@rejected_license_to_warning.accepted b/test/birdie_snapshots/javascript@warning@rejected_license_to_warning.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@rejected_license_to_warning.accepted rename to test/birdie_snapshots/javascript@warning@rejected_license_to_warning.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_deprecated.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_deprecated.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_deprecated.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_deprecated.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_deprecated_none.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_deprecated_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_deprecated_none.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_deprecated_none.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_invalid.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_invalid.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_invalid.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_invalid.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_invalid_none.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_invalid_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_invalid_none.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_invalid_none.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_other.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_other.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_other.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_other.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_other_none.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_other_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_other_none.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_other_none.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_renamed.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_renamed.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_renamed.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_renamed.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_renamed_none.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_renamed_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_renamed_none.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_renamed_none.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_security.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_security.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_security.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_security.accepted diff --git a/birdie_snapshots/javascript@warning@retired_to_warning_security_none.accepted b/test/birdie_snapshots/javascript@warning@retired_to_warning_security_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning@retired_to_warning_security_none.accepted rename to test/birdie_snapshots/javascript@warning@retired_to_warning_security_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@adv_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@adv_to_warning.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@adv_to_warning.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@adv_to_warning.accepted diff --git a/test/birdie_snapshots/javascript@warning_format_as_json@git_deps_to_warnings.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@git_deps_to_warnings.accepted new file mode 100644 index 0000000..b9772ee --- /dev/null +++ b/test/birdie_snapshots/javascript@warning_format_as_json@git_deps_to_warnings.accepted @@ -0,0 +1,16 @@ +--- +version: 2.0.0 +title: Javascript@warning_format_as_json@git_deps_to_warnings +--- +#( + "c", + "{ + "id": null, + "package": "git-dependencies", + "version": null, + "warning_reason": "Info", + "dependency_type": "Direct", + "severity": "info", + "reason": "Info: git dependencies have limited support (retirement and license checks are not performed): c" +}", +) diff --git a/test/birdie_snapshots/javascript@warning_format_as_json@info_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@info_to_warning.accepted new file mode 100644 index 0000000..1741f7c --- /dev/null +++ b/test/birdie_snapshots/javascript@warning_format_as_json@info_to_warning.accepted @@ -0,0 +1,16 @@ +--- +version: 2.0.0 +title: Javascript@warning_format_as_json@info_to_warning +--- +#( + "missing-package", + "{ + "id": null, + "package": "missing-package", + "version": null, + "warning_reason": "Info", + "dependency_type": "Direct", + "severity": "info", + "reason": "Info: package 'missing-package' is not a dependency" +}", +) diff --git a/birdie_snapshots/javascript@warning_format_as_json@rejected_license_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@rejected_license_to_warning.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@rejected_license_to_warning.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@rejected_license_to_warning.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_deprecated.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_deprecated.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_deprecated.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_deprecated.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_deprecated_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_deprecated_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_deprecated_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_deprecated_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_invalid.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_invalid.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_invalid.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_invalid.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_invalid_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_invalid_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_invalid_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_invalid_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_other.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_other.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_other.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_other.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_other_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_other_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_other_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_other_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_renamed.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_renamed.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_renamed.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_renamed.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_renamed_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_renamed_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_renamed_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_renamed_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_security.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_security.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_security.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_security.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_security_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_security_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_security_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_json@retired_to_warning_security_none.accepted diff --git a/test/birdie_snapshots/javascript@warning_format_as_sarif@adv_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_sarif@adv_to_warning.accepted new file mode 100644 index 0000000..8ff0bca --- /dev/null +++ b/test/birdie_snapshots/javascript@warning_format_as_sarif@adv_to_warning.accepted @@ -0,0 +1,53 @@ +--- +version: 2.0.0 +title: Javascript@warning_format_as_sarif@adv_to_warning +--- +#( + Warning( + advisory_id: Some("GHSA-test-1234"), + package: "package for warning tests", + version: Some("pre1.2.3-build"), + reason: "example vulnerability", + warning_reason_code: WarningReasonVulnerable, + severity: SeverityHigh, + dep: DirectDep, + ), + "{ + "tool": { + "driver": { + "name": "go_over", + "version": "4.0.0", + "rules": [ + { + "id": "GHSA-test-1234", + "shortDescription": { + "text": "GHSA-test-1234" + } + } + ] + } + }, + "results": [ + { + "ruleId": "GHSA-test-1234", + "level": "error", + "message": { + "text": "package for warning tests@pre1.2.3-build: example vulnerability" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "backend/manifest.toml" + }, + "region": { + "startLine": 1, + "startColumn": 1 + } + } + } + ] + } + ] +}", +) diff --git a/test/birdie_snapshots/javascript@warning_format_as_sarif@info_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_sarif@info_to_warning.accepted new file mode 100644 index 0000000..1abe012 --- /dev/null +++ b/test/birdie_snapshots/javascript@warning_format_as_sarif@info_to_warning.accepted @@ -0,0 +1,53 @@ +--- +version: 2.0.0 +title: Javascript@warning_format_as_sarif@info_to_warning +--- +#( + Warning( + advisory_id: None, + package: "missing-package", + version: None, + reason: "Info: package 'missing-package' is not a dependency", + warning_reason_code: WarningReasonInfo, + severity: SeverityInfo, + dep: DirectDep, + ), + "{ + "tool": { + "driver": { + "name": "go_over", + "version": "4.0.0", + "rules": [ + { + "id": "go-over/info:info", + "shortDescription": { + "text": "go-over/info:info" + } + } + ] + } + }, + "results": [ + { + "ruleId": "go-over/info:info", + "level": "note", + "message": { + "text": "missing-package: Info: package 'missing-package' is not a dependency" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "backend/manifest.toml" + }, + "region": { + "startLine": 1, + "startColumn": 1 + } + } + } + ] + } + ] +}", +) diff --git a/birdie_snapshots/javascript@warning_format_as_string@adv_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@adv_to_warning.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@adv_to_warning.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@adv_to_warning.accepted diff --git a/test/birdie_snapshots/javascript@warning_format_as_string@git_deps_to_warnings.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@git_deps_to_warnings.accepted new file mode 100644 index 0000000..a72dcc9 --- /dev/null +++ b/test/birdie_snapshots/javascript@warning_format_as_string@git_deps_to_warnings.accepted @@ -0,0 +1,15 @@ +--- +version: 2.0.0 +title: Javascript@warning_format_as_string@git_deps_to_warnings +--- +#( + "c", + "ID: null +Package: git-dependencies +Version: null +WarningReason: Info +Dependency Type: Direct +Severity: info +Reason: Info: git dependencies have limited support (retirement and license checks are not performed): c +", +) diff --git a/test/birdie_snapshots/javascript@warning_format_as_string@info_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@info_to_warning.accepted new file mode 100644 index 0000000..bba8ae1 --- /dev/null +++ b/test/birdie_snapshots/javascript@warning_format_as_string@info_to_warning.accepted @@ -0,0 +1,15 @@ +--- +version: 2.0.0 +title: Javascript@warning_format_as_string@info_to_warning +--- +#( + "missing-package", + "ID: null +Package: missing-package +Version: null +WarningReason: Info +Dependency Type: Direct +Severity: info +Reason: Info: package 'missing-package' is not a dependency +", +) diff --git a/birdie_snapshots/javascript@warning_format_as_string@rejected_license_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@rejected_license_to_warning.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@rejected_license_to_warning.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@rejected_license_to_warning.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_deprecated.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_deprecated.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_deprecated.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_deprecated.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_deprecated_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_deprecated_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_deprecated_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_deprecated_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_invalid.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_invalid.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_invalid.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_invalid.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_invalid_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_invalid_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_invalid_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_invalid_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other.accepted similarity index 95% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other.accepted index e205cc7..092b2a6 100644 --- a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other.accepted +++ b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@warning_format_as_string@retired_to_warning_other --- #( @@ -22,7 +22,7 @@ title: Javascript@warning_format_as_string@retired_to_warning_other message: Some("It's an example man"), ), ), - "ID: null + "ID: null Package: package for warning tests Version: pre1.2.3-build WarningReason: Retired @@ -30,4 +30,4 @@ Dependency Type: Direct Severity: package-retired:it's an example man Reason: other: It's an example man ", -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other_none.accepted similarity index 94% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other_none.accepted index 94cf09b..e8c7ac8 100644 --- a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other_none.accepted +++ b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_other_none.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@warning_format_as_string@retired_to_warning_other_none --- #( @@ -22,7 +22,7 @@ title: Javascript@warning_format_as_string@retired_to_warning_other_none message: None, ), ), - "ID: null + "ID: null Package: package for warning tests Version: pre1.2.3-build WarningReason: Retired @@ -30,4 +30,4 @@ Dependency Type: Direct Severity: package-retired:unknown Reason: other ", -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_renamed.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_renamed.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_renamed.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_renamed.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_renamed_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_renamed_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_renamed_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_renamed_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_security.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_security.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_security.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_security.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_security_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_security_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_security_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string@retired_to_warning_security_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@adv_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@adv_to_warning.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@adv_to_warning.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@adv_to_warning.accepted diff --git a/test/birdie_snapshots/javascript@warning_format_as_string_minimal@git_deps_to_warnings.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@git_deps_to_warnings.accepted new file mode 100644 index 0000000..874b93b --- /dev/null +++ b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@git_deps_to_warnings.accepted @@ -0,0 +1,9 @@ +--- +version: 2.0.0 +title: Javascript@warning_format_as_string_minimal@git_deps_to_warnings +--- +#( + "c", + "git-dependencies: info +", +) diff --git a/test/birdie_snapshots/javascript@warning_format_as_string_minimal@info_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@info_to_warning.accepted new file mode 100644 index 0000000..a7258ce --- /dev/null +++ b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@info_to_warning.accepted @@ -0,0 +1,9 @@ +--- +version: 2.0.0 +title: Javascript@warning_format_as_string_minimal@info_to_warning +--- +#( + "missing-package", + "missing-package: info +", +) diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@rejected_license_to_warning.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@rejected_license_to_warning.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@rejected_license_to_warning.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@rejected_license_to_warning.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_deprecated.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_deprecated.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_deprecated.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_deprecated.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_deprecated_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_deprecated_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_deprecated_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_deprecated_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_invalid.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_invalid.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_invalid.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_invalid.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_invalid_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_invalid_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_invalid_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_invalid_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other.accepted similarity index 86% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other.accepted index c12e766..fccdca9 100644 --- a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other.accepted +++ b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@warning_format_as_string_minimal@retired_to_warning_other --- #( @@ -22,6 +22,6 @@ title: Javascript@warning_format_as_string_minimal@retired_to_warning_other message: Some("It's an example man"), ), ), - "package for warning tests-pre1.2.3-build: package-retired:it's an example man + "package for warning tests-pre1.2.3-build: package-retired:it's an example man ", -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other_none.accepted similarity index 85% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other_none.accepted index d651cf9..1580b81 100644 --- a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other_none.accepted +++ b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_other_none.accepted @@ -1,5 +1,5 @@ --- -version: 1.3.1 +version: 2.0.0 title: Javascript@warning_format_as_string_minimal@retired_to_warning_other_none --- #( @@ -22,6 +22,6 @@ title: Javascript@warning_format_as_string_minimal@retired_to_warning_other_none message: None, ), ), - "package for warning tests-pre1.2.3-build: package-retired:unknown + "package for warning tests-pre1.2.3-build: package-retired:unknown ", -) \ No newline at end of file +) diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_renamed.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_renamed.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_renamed.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_renamed.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_renamed_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_renamed_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_renamed_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_renamed_none.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_security.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_security.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_security.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_security.accepted diff --git a/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_security_none.accepted b/test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_security_none.accepted similarity index 100% rename from birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_security_none.accepted rename to test/birdie_snapshots/javascript@warning_format_as_string_minimal@retired_to_warning_security_none.accepted diff --git a/test/config_test.gleam b/test/config_test.gleam index 1bb4783..fd5c472 100644 --- a/test/config_test.gleam +++ b/test/config_test.gleam @@ -1,9 +1,11 @@ +import gleam/list import gleam/option.{None} import gleamsver.{parse} import go_over/advisories/advisories.{Advisory} import go_over/config.{ - filter_advisory_ids, filter_dev_dependencies, filter_packages, filter_severity, - read_config, + filter_advisory_ids, filter_dev_dependencies, filter_package_warnings, + filter_packages, filter_severity, read_config, unnecessary_ignore_id_warnings, + unnecessary_ignore_warnings, } import go_over/hex/puller import go_over/packages.{Package} @@ -189,12 +191,6 @@ pub fn spin_up_test() { let conf = test_spin_up("force", ["--force"]) assert conf.force - let conf = test_spin_up("outdated", ["--outdated"]) - assert conf.outdated - - let conf = test_spin_up("ignore_indirect", ["--ignore-indirect"]) - assert conf.ignore_indirect - let conf = test_spin_up("verbose", ["--verbose"]) assert conf.verbose } @@ -213,12 +209,14 @@ pub fn spin_up_format_test() { const empty_flags = config.Flags( force: False, outdated: False, - ignore_indirect: False, verbose: False, format: option.None, global: False, local: False, puller: option.None, + single_root: option.None, + workspace_root: option.None, + sarif_output: option.None, ) pub fn merge_flags_and_config_flags_only_test() { @@ -231,12 +229,6 @@ pub fn merge_flags_and_config_flags_only_test() { ) == Ok(config.Config(..empty_conf(), outdated: True)) - assert config.merge_flags_and_config( - config.Flags(..empty_flags, ignore_indirect: True), - empty_conf(), - ) - == Ok(config.Config(..empty_conf(), ignore_indirect: True)) - assert config.merge_flags_and_config( config.Flags(..empty_flags, force: True), empty_conf(), @@ -312,6 +304,195 @@ pub fn merge_flags_and_config_conf_only_test() { == Ok(config.Config(..empty_conf(), format: config.Detailed)) } +pub fn unnecessary_ignore_warnings_test() { + let assert Ok(v) = parse("1.1.1") + let a = Package("a", v, "", True, packages.PackageSourceHex) + let b = Package("b", v, "", False, packages.PackageSourceHex) + let manifest = [a, b] + + let conf = + config.Config( + ..empty_conf(), + ignore_packages: ["a", "b", "missing"], + ignore_severity: ["critical", "missing-severity"], + ignore_indirect: True, + ) + + let audit_warning = + Warning( + None, + "a", + None, + "", + warning.WarningReasonVulnerable, + warning.SeverityCritical, + warning.DirectDep, + ) + + assert unnecessary_ignore_warnings(conf, manifest, [audit_warning], [], []) + == [ + warning.info_to_warning( + "b", + "Info: package 'b' did not match any warnings", + ), + warning.info_to_warning( + "missing", + "Info: package 'missing' is not a dependency", + ), + warning.info_to_warning( + "missing-severity", + "Info: severity 'missing-severity' did not match any warnings", + ), + ] +} + +pub fn unnecessary_ignore_ignored_package_with_warning_test() { + let assert Ok(v) = parse("1.1.1") + let a = Package("a", v, "", True, packages.PackageSourceHex) + let conf = config.Config(..empty_conf(), ignore_packages: ["a"]) + + let audit_warning = + Warning( + None, + "a", + None, + "", + warning.WarningReasonVulnerable, + warning.SeverityCritical, + warning.DirectDep, + ) + + assert unnecessary_ignore_warnings(conf, [a], [audit_warning], [], []) == [] +} + +pub fn validate_workspace_formats_test() { + let assert Error(msg) = + config.validate_workspace_formats([ + #(config.Minimal, "a"), + #(config.JSON, "b"), + ]) + + assert msg + == "workspace projects have mismatched output formats; set format consistently or use --format" + + assert config.validate_workspace_formats([ + #(config.Minimal, "a"), + #(config.Minimal, "b"), + ]) + == Ok(Nil) +} + +pub fn unnecessary_ignore_license_warnings_test() { + let conf = + config.Config(..empty_conf(), allowed_licenses: [ + "MIT", + "Apache-2.0", + "WTFPL", + ]) + + assert unnecessary_ignore_warnings(conf, [], [], ["MIT", "Apache-2.0"], []) + == [ + warning.info_to_warning( + "WTFPL", + "Info: license 'WTFPL' did not match any dependency licenses", + ), + ] +} + +pub fn filter_package_warnings_test() { + let full = test_read_config("test/testdata/gleam/full.toml") + let warning_a = + Warning( + None, + "a", + None, + "", + warning.WarningReasonVulnerable, + warning.SeverityCritical, + warning.DirectDep, + ) + let warning_c = + Warning( + None, + "c", + None, + "", + warning.WarningReasonVulnerable, + warning.SeverityCritical, + warning.DirectDep, + ) + + assert filter_package_warnings(full, [warning_a, warning_c]) == [warning_c] +} + +pub fn unnecessary_ignore_indirect_test() { + let assert Ok(v) = parse("1.1.1") + let direct = Package("a", v, "", True, packages.PackageSourceHex) + let conf = config.Config(..empty_conf(), ignore_indirect: True) + + assert unnecessary_ignore_warnings(conf, [direct], [], [], []) + == [ + warning.info_to_warning( + "indirect", + "Info: indirect=true has no effect (no indirect dependencies)", + ), + ] +} + +pub fn unnecessary_ignore_dev_dependencies_test() { + let assert Ok(v) = parse("1.1.1") + let pkg = Package("a", v, "", True, packages.PackageSourceHex) + let no_dev_deps = config.Config(..empty_conf(), ignore_dev_dependencies: True) + let missing_dev_deps = + config.Config( + ..empty_conf(), + dev_deps: ["not-in-manifest"], + ignore_dev_dependencies: True, + ) + + assert unnecessary_ignore_warnings(no_dev_deps, [pkg], [], [], []) + == [ + warning.info_to_warning( + "dev_dependencies", + "Info: dev_dependencies=true has no effect (no dev-dependencies configured)", + ), + ] + + assert unnecessary_ignore_warnings(missing_dev_deps, [pkg], [], [], []) + == [ + warning.info_to_warning( + "dev_dependencies", + "Info: dev_dependencies=true has no effect (no dev-dependencies in manifest)", + ), + ] +} + +pub fn unnecessary_ignore_id_warnings_test() { + let conf = config.Config(..empty_conf(), ignore_ids: ["known", "unknown"]) + let advisories = [ + Advisory("known", "present", "", [], ""), + Advisory("other", "missing", "", [], ""), + ] + + assert unnecessary_ignore_id_warnings(conf, ["present"], advisories) + == [ + warning.info_to_warning( + "unknown", + "Info: advisory id 'unknown' is unknown", + ), + ] + + let wrong_package = config.Config(..empty_conf(), ignore_ids: ["other"]) + + assert unnecessary_ignore_id_warnings(wrong_package, ["present"], advisories) + == [ + warning.info_to_warning( + "other", + "Info: advisory id 'other' does not apply to any dependency", + ), + ] +} + pub fn merge_flags_and_config_both_test() { assert config.merge_flags_and_config( config.Flags(..empty_flags, format: option.Some(config.JSON)), @@ -355,3 +536,137 @@ pub fn merge_flags_and_config_both_test() { ) == Ok(config.Config(..empty_conf(), global: False)) } + +pub fn read_dev_dependencies_underscore_test() { + let conf = read_config("test/testdata/gleam/dev_dependencies_underscore.toml") + assert list.length(conf.dev_deps) == 2 + assert list.contains(conf.dev_deps, "gleeunit") + assert list.contains(conf.dev_deps, "birdie") +} + +pub fn read_workspace_max_depth_test() { + let conf = read_config("test/testdata/gleam/workspace_max_depth.toml") + assert conf.workspace_max_depth == 5 +} + +pub fn normalize_workspace_argv_test() { + assert config.normalize_workspace_argv(["--workspace"]) + == ["--workspace", "."] + assert config.normalize_workspace_argv(["--workspace", "backend"]) + == ["--workspace", "backend"] + assert config.normalize_workspace_argv(["--workspace", "--local"]) + == ["--workspace", ".", "--local"] + assert config.normalize_workspace_argv(["--workspace", "--format", "json"]) + == ["--workspace", ".", "--format", "json"] +} + +pub fn merge_flags_format_overrides_workspace_project_test() { + let json_project = config.Config(..empty_conf(), format: config.JSON) + let minimal_project = config.Config(..empty_conf(), format: config.Minimal) + let cli_sarif = config.Flags(..empty_flags, format: option.Some(config.SARIF)) + + assert config.merge_flags_and_config(cli_sarif, json_project) + == Ok(config.Config(..json_project, format: config.SARIF)) + assert config.merge_flags_and_config(cli_sarif, minimal_project) + == Ok(config.Config(..minimal_project, format: config.SARIF)) +} + +pub fn spin_up_root_test() { + let assert Ok(conf) = + empty_conf() + |> config.spin_up(["--root", "backend"]) + + assert conf.single_root == option.Some("backend") + assert conf.workspace_root == option.None +} + +pub fn spin_up_workspace_test() { + let assert Ok(conf) = + empty_conf() + |> config.spin_up(["--workspace", "monorepo"]) + + assert conf.workspace_root == option.Some("monorepo") + assert conf.single_root == option.None +} + +pub fn parse_sarif_output_test() { + let assert Ok(flags) = + config.parse_flags(["--format", "sarif", "--sarif-output", "out.sarif"]) + + assert flags.sarif_output == option.Some("out.sarif") + assert flags.format == option.Some(config.SARIF) +} + +pub fn merge_root_and_workspace_error_test() { + let assert Error(msg) = + config.merge_flags_and_config( + config.Flags( + ..empty_flags, + single_root: option.Some("a"), + workspace_root: option.Some("b"), + ), + empty_conf(), + ) + + assert msg == "cannot set --root and --workspace" +} + +pub fn default_workspace_max_depth_test() { + assert empty_conf().workspace_max_depth == 3 +} + +pub fn validate_workspace_formats_empty_test() { + assert config.validate_workspace_formats([]) == Ok(Nil) +} + +pub fn validate_workspace_formats_single_test() { + assert config.validate_workspace_formats([#(config.JSON, "only")]) == Ok(Nil) +} + +pub fn validate_workspace_formats_three_matching_test() { + assert config.validate_workspace_formats([ + #(config.Detailed, "a"), + #(config.Detailed, "b"), + #(config.Detailed, "c"), + ]) + == Ok(Nil) +} + +pub fn validate_workspace_formats_detailed_mismatch_test() { + let assert Error(msg) = + config.validate_workspace_formats([ + #(config.Detailed, "a"), + #(config.Minimal, "b"), + ]) + + assert msg + == "workspace projects have mismatched output formats; set format consistently or use --format" +} + +pub fn normalize_workspace_argv_noop_test() { + assert config.normalize_workspace_argv(["--root", "backend"]) + == ["--root", "backend"] + assert config.normalize_workspace_argv([]) == [] +} + +pub fn parse_flags_workspace_default_path_test() { + let assert Ok(flags) = config.parse_flags(["--workspace"]) + + assert flags.workspace_root == option.Some(".") + assert flags.single_root == option.None +} + +pub fn parse_flags_workspace_with_path_test() { + let assert Ok(flags) = config.parse_flags(["--workspace", "monorepo"]) + + assert flags.workspace_root == option.Some("monorepo") +} + +pub fn spin_up_workspace_default_path_test() { + let assert Ok(conf) = + empty_conf() + |> config.spin_up(["--workspace"]) + + assert conf.workspace_root == option.Some(".") + assert conf.single_root == option.None +} diff --git a/test/hex_test.gleam b/test/hex_test.gleam index 6d7eb2c..ef2b476 100644 --- a/test/hex_test.gleam +++ b/test/hex_test.gleam @@ -6,19 +6,15 @@ fn parse(path: String) { let assert Ok(input) = simplifile.read(path) input - |> hex.decode_latest_stable_version_and_licenses - |> go_over_test.birdie_snap_with_input( - input, - "decode_latest_stable_version_and_licenses@" <> path, - ) + |> hex.decode_licenses + |> go_over_test.birdie_snap_with_input(input, "decode_licenses@" <> path) } -pub fn decode_latest_stable_version_and_licenses_test() { +pub fn decode_licenses_test() { let assert Ok(_) = parse("test/testdata/hex/empty_licenses.json") let assert Ok(_) = parse("test/testdata/hex/full.json") let assert Ok(_) = parse("test/testdata/hex/multi_license.json") + let assert Ok(_) = parse("test/testdata/hex/no_version.json") let assert Error(_) = parse("test/testdata/hex/no_license.json") let assert Error(_) = parse("test/testdata/hex/no_meta.json") - let assert Error(_) = parse("test/testdata/hex/no_version.json") - let assert Ok(_) = parse("test/testdata/hex/version_null.json") } diff --git a/test/integration_test.gleam b/test/integration_test.gleam new file mode 100644 index 0000000..68523d5 --- /dev/null +++ b/test/integration_test.gleam @@ -0,0 +1,249 @@ +import gleam/json +import gleam/list +import gleam/option +import gleam/string +import gleamsver +import go_over +import go_over/advisories/advisories +import go_over/config +import go_over/packages +import go_over/sarif +import go_over/warning +import go_over/workspace +import go_over_test + +const workspace = "test/testdata/workspace" + +const workspace_depth = "test/testdata/workspace_depth" + +const workspace_root_project = "test/testdata/workspace_root_project" + +const audit_flags = config.Flags( + force: False, + outdated: False, + verbose: False, + format: option.None, + global: False, + local: True, + puller: option.None, + single_root: option.None, + workspace_root: option.None, + sarif_output: option.None, +) + +pub fn skipped_workspace_warnings_test() { + let path = "test/testdata/workspace_depth/too_deep/nested/nested/project" + let warnings = go_over.skipped_workspace_warnings([path]) + + assert list.length(warnings) == 1 + let assert Ok(w) = list.first(warnings) + assert warning.is_info(w) + assert string.contains(w.reason, path) + assert string.contains(w.reason, "exceeds workspace_max_depth") +} + +pub fn audit_workspace_fixture_test() { + let assert Ok(result) = + go_over.audit_project(audit_flags, workspace <> "/app_a") + + assert result.project_root == workspace <> "/app_a" + assert result.fatal_warnings == [] + assert result.outdated_failed == False +} + +pub fn audit_all_discovered_workspace_projects_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace, 3) + + assert skipped == [] + + let results = + list.map(projects, fn(project_root) { + let assert Ok(result) = go_over.audit_project(audit_flags, project_root) + result + }) + + assert list.length(results) == 2 + assert list.all(results, fn(r) { r.fatal_warnings == [] }) +} + +pub fn workspace_sarif_multiple_runs_test() { + let info = warning.info_to_warning("skipped", "Info: project was skipped") + + let runs = [ + #(workspace <> "/app_a", [info]), + #(workspace <> "/app_b", []), + ] + + let sarif_json = sarif.to_sarif_log(runs) |> json.to_string() + + assert list.length(runs) == 2 + assert string.contains(sarif_json, workspace <> "/app_a/manifest.toml") + assert string.contains(sarif_json, "\"results\":[]") +} + +pub fn warnings_for_json_results_test() { + let info = warning.info_to_warning("x", "Info: example") + + let fatal = warning.info_to_warning("y", "fatal placeholder") + + let results = [ + go_over.AuditResult( + project_root: "a", + fatal_warnings: [fatal], + info_warnings: [info], + outdated_failed: False, + format: config.JSON, + ), + ] + + assert list.length(go_over.warnings_for_json_results(results)) == 2 +} + +pub fn validate_workspace_formats_integration_test() { + let assert Error(_) = + config.validate_workspace_formats([ + #(config.Minimal, "a"), + #(config.JSON, "b"), + ]) + + assert config.validate_workspace_formats([ + #(config.SARIF, "a"), + #(config.SARIF, "b"), + ]) + == Ok(Nil) +} + +pub fn skipped_workspace_warnings_multiple_test() { + let skipped = [ + workspace_depth <> "/at_max/nested/project", + workspace_depth <> "/too_deep/nested/nested/project", + ] + + let warnings = go_over.skipped_workspace_warnings(skipped) + + assert list.length(warnings) == 2 + assert list.all(warnings, warning.is_info) + assert list.all(warnings, fn(w) { + string.contains(w.reason, "exceeds workspace_max_depth") + }) +} + +pub fn discover_depth_skipped_warnings_integration_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_depth, 1) + + assert projects == [workspace_depth <> "/shallow"] + assert list.length(skipped) == 2 + + let warnings = go_over.skipped_workspace_warnings(skipped) + + assert list.length(warnings) == list.length(skipped) + let assert Ok(first) = list.first(warnings) + assert string.contains( + first.reason, + list.first(skipped) + |> fn(r) { + case r { + Ok(s) -> s + Error(_) -> "" + } + }, + ) +} + +pub fn audit_workspace_app_b_test() { + let assert Ok(result) = + go_over.audit_project(audit_flags, workspace <> "/app_b") + + assert result.project_root == workspace <> "/app_b" + assert result.fatal_warnings == [] +} + +pub fn audit_workspace_root_project_test() { + let assert Ok(result) = + go_over.audit_project(audit_flags, workspace_root_project) + + assert result.project_root == workspace_root_project + assert result.fatal_warnings == [] +} + +pub fn warnings_for_json_results_multiple_test() { + let info_a = warning.info_to_warning("a", "Info: first") + let info_b = warning.info_to_warning("b", "Info: second") + let fatal = warning.info_to_warning("c", "fatal placeholder") + + let results = [ + go_over.AuditResult( + project_root: "a", + fatal_warnings: [fatal], + info_warnings: [info_a], + outdated_failed: False, + format: config.JSON, + ), + go_over.AuditResult( + project_root: "b", + fatal_warnings: [], + info_warnings: [info_b], + outdated_failed: False, + format: config.JSON, + ), + ] + + let warnings = go_over.warnings_for_json_results(results) + + assert list.length(warnings) == 3 + assert list.any(warnings, fn(w) { w.reason == "Info: first" }) + assert list.any(warnings, fn(w) { w.reason == "Info: second" }) + assert list.any(warnings, fn(w) { w.reason == "fatal placeholder" }) +} + +pub fn skipped_workspace_warning_message_test() { + let path = "path/to/project" + let assert Ok(w) = go_over.skipped_workspace_warnings([path]) |> list.first + + assert w.reason + == "Info: project at '" + <> path + <> "' was skipped (exceeds workspace_max_depth)" +} + +pub fn sarif_log_includes_info_test() { + let info = + warning.info_to_warning( + "missing-package", + "Info: package 'missing-package' is not a dependency", + ) + + let fatal = + warning.adv_to_warning( + packages.Package( + "pkg", + gleamsver.SemVer(1, 0, 0, "", ""), + "1.0.0", + True, + packages.PackageSourceHex, + ), + [ + advisories.Advisory("GHSA-test", "pkg", "high", [], "example"), + ], + ) + |> list.first + |> fn(r) { + case r { + Ok(w) -> w + Error(_) -> panic as "expected warning" + } + } + + let sarif_json = + sarif.to_sarif_log([#("backend", [info, fatal])]) + |> json.to_string() + + assert string.contains(sarif_json, "\"level\"") + assert string.contains(sarif_json, "note") + assert string.contains(sarif_json, "error") + assert string.contains(sarif_json, "4.0.0") + + go_over_test.birdie_snap(sarif_json, "integration@sarif_log_includes_info") +} diff --git a/test/sources_test.gleam b/test/sources_test.gleam index f8a077a..92715c2 100644 --- a/test/sources_test.gleam +++ b/test/sources_test.gleam @@ -5,13 +5,12 @@ import go_over/hex/puller.{Mock} import go_over/packages import go_over/sources import go_over/warning.{ - IndirectDep, Warning, WarningReasonOutdated, WarningReasonRejectedLicense, - WarningReasonRetired, + IndirectDep, Warning, WarningReasonRejectedLicense, WarningReasonRetired, } const conf = Config( dev_deps: [], - outdated: True, + outdated: False, ignore_indirect: False, force: True, format: Minimal, @@ -23,6 +22,9 @@ const conf = Config( ignore_severity: [], ignore_ids: [], ignore_dev_dependencies: False, + workspace_max_depth: 3, + single_root: option.None, + workspace_root: option.None, ) const pkgs = [ @@ -60,13 +62,16 @@ pub fn get_retired_warnings_test() { } pub fn get_rejected_license_test() { - assert sources.get_hex_warnings( + let #(warnings, _licenses) = + sources.get_hex_warnings( pkgs, Config( ..conf, puller: Mock("test/testdata/hex/rejected_licenses/bad_license.json"), ), ) + + assert warnings == [ Warning( None, @@ -79,36 +84,14 @@ pub fn get_rejected_license_test() { ), ] - assert sources.get_hex_warnings( + let #(warnings, _licenses) = + sources.get_hex_warnings( pkgs, Config( ..conf, puller: Mock("test/testdata/hex/rejected_licenses/good_license.json"), ), ) - == [] -} -pub fn get_outdated_test() { - assert sources.get_hex_warnings( - pkgs, - Config(..conf, puller: Mock("test/testdata/hex/outdated/outdated.json")), - ) - == [ - Warning( - None, - "name", - Some("1.1.1"), - "New Version: '1.2.3' exists", - WarningReasonOutdated, - warning.SeverityPackageOutdated, - IndirectDep, - ), - ] - - assert sources.get_hex_warnings( - pkgs, - Config(..conf, puller: Mock("test/testdata/hex/outdated/up_to_date.json")), - ) - == [] + assert warnings == [] } diff --git a/test/testdata/gleam/basic.toml b/test/testdata/gleam/basic.toml index 87cb3cc..a7867ea 100644 --- a/test/testdata/gleam/basic.toml +++ b/test/testdata/gleam/basic.toml @@ -1,5 +1,5 @@ [go-over] -cache = false +force = true puller = "httpie" format = "detailed" diff --git a/test/testdata/gleam/dev_dependencies_underscore.toml b/test/testdata/gleam/dev_dependencies_underscore.toml new file mode 100644 index 0000000..f914426 --- /dev/null +++ b/test/testdata/gleam/dev_dependencies_underscore.toml @@ -0,0 +1,3 @@ +[dev_dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" +birdie = ">= 2.0.0 and < 3.0.0" diff --git a/test/testdata/gleam/workspace_max_depth.toml b/test/testdata/gleam/workspace_max_depth.toml new file mode 100644 index 0000000..475f90c --- /dev/null +++ b/test/testdata/gleam/workspace_max_depth.toml @@ -0,0 +1,2 @@ +[go-over] +workspace_max_depth = 5 diff --git a/test/testdata/hex/empty_licenses.json b/test/testdata/hex/empty_licenses.json index cd28909..a94ba51 100644 --- a/test/testdata/hex/empty_licenses.json +++ b/test/testdata/hex/empty_licenses.json @@ -1,6 +1,5 @@ { "meta": { "licenses": [] - }, - "latest_stable_version": "2.1.0" + } } diff --git a/test/testdata/hex/full.json b/test/testdata/hex/full.json index 9c0e999..f5c8d43 100644 --- a/test/testdata/hex/full.json +++ b/test/testdata/hex/full.json @@ -1,6 +1,5 @@ { "meta": { "licenses": ["MIT"] - }, - "latest_stable_version": "2.1.0" + } } diff --git a/test/testdata/hex/multi_license.json b/test/testdata/hex/multi_license.json index b9ab856..0b5148f 100644 --- a/test/testdata/hex/multi_license.json +++ b/test/testdata/hex/multi_license.json @@ -1,6 +1,5 @@ { "meta": { - "licenses": ["foo", "BAR", "baz"] - }, - "latest_stable_version": "2.1.0" + "licenses": ["MIT", "Apache-2.0"] + } } diff --git a/test/testdata/hex/no_license.json b/test/testdata/hex/no_license.json index 4e58611..6da7422 100644 --- a/test/testdata/hex/no_license.json +++ b/test/testdata/hex/no_license.json @@ -1,5 +1,3 @@ { - "meta": { - }, - "latest_stable_version": "2.1.0" + "meta": {} } diff --git a/test/testdata/hex/no_meta.json b/test/testdata/hex/no_meta.json index 2eb2012..0967ef4 100644 --- a/test/testdata/hex/no_meta.json +++ b/test/testdata/hex/no_meta.json @@ -1,3 +1 @@ -{ - "latest_stable_version": "2.1.0" -} +{} diff --git a/test/testdata/hex/outdated/outdated.json b/test/testdata/hex/outdated/outdated.json deleted file mode 100644 index 65633bf..0000000 --- a/test/testdata/hex/outdated/outdated.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "meta": { - "links": {}, - "description": "out of date", - "licenses": ["MIT"], - "maintainers": [] - }, - "name": "outdated", - "url": "outdated", - "owners": [], - "inserted_at": "2024-05-30T22:39:23.118433Z", - "updated_at": "2025-01-20T17:52:08.078591Z", - "repository": "hexpm", - "releases": [], - "downloads": { "all": 1, "day": 2, "recent": 3, "week": 4 }, - "latest_version": "1.1.1", - "docs_html_url": "outdated/", - "retirements": {}, - "configs": { - "erlang.mk": "dep_outdated = hex 1.1.1", - "mix.exs": "{:outdated, \"~> 2.4\"}", - "rebar.config": "{outdated, \"1.1.1\"}" - }, - "html_url": "/packages/outdated", - "latest_stable_version": "1.2.3" -} diff --git a/test/testdata/hex/outdated/up_to_date.json b/test/testdata/hex/outdated/up_to_date.json deleted file mode 100644 index f76d828..0000000 --- a/test/testdata/hex/outdated/up_to_date.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "meta": { - "links": {}, - "description": "up to date", - "licenses": ["MIT"], - "maintainers": [] - }, - "name": "up_to_date", - "url": "up_to_date", - "owners": [], - "inserted_at": "2024-05-30T22:39:23.118433Z", - "updated_at": "2025-01-20T17:52:08.078591Z", - "repository": "hexpm", - "releases": [], - "downloads": { "all": 1, "day": 2, "recent": 3, "week": 4 }, - "latest_version": "1.1.1", - "docs_html_url": "up_to_date/", - "retirements": {}, - "configs": { - "erlang.mk": "dep_up_to_date = hex 1.1.1", - "mix.exs": "{:up_to_date, \"~> 2.4\"}", - "rebar.config": "{up_to_date, \"1.1.1\"}" - }, - "html_url": "/packages/up_to_date", - "latest_stable_version": "1.1.1" -} diff --git a/test/testdata/hex/version_null.json b/test/testdata/hex/version_null.json deleted file mode 100644 index f261936..0000000 --- a/test/testdata/hex/version_null.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "meta": { - "licenses": ["bin"] - }, - "latest_stable_version": null -} diff --git a/test/testdata/workspace/app_a/gleam.toml b/test/testdata/workspace/app_a/gleam.toml new file mode 100644 index 0000000..abe4103 --- /dev/null +++ b/test/testdata/workspace/app_a/gleam.toml @@ -0,0 +1,5 @@ +name = "app_a" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace/app_a/manifest.toml b/test/testdata/workspace/app_a/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace/app_a/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace/app_b/gleam.toml b/test/testdata/workspace/app_b/gleam.toml new file mode 100644 index 0000000..1741694 --- /dev/null +++ b/test/testdata/workspace/app_b/gleam.toml @@ -0,0 +1,5 @@ +name = "app_b" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace/app_b/manifest.toml b/test/testdata/workspace/app_b/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace/app_b/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_depth/at_max/nested/project/gleam.toml b/test/testdata/workspace_depth/at_max/nested/project/gleam.toml new file mode 100644 index 0000000..28796df --- /dev/null +++ b/test/testdata/workspace_depth/at_max/nested/project/gleam.toml @@ -0,0 +1,5 @@ +name = "at_max" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_depth/at_max/nested/project/manifest.toml b/test/testdata/workspace_depth/at_max/nested/project/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_depth/at_max/nested/project/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_depth/shallow/gleam.toml b/test/testdata/workspace_depth/shallow/gleam.toml new file mode 100644 index 0000000..821e49e --- /dev/null +++ b/test/testdata/workspace_depth/shallow/gleam.toml @@ -0,0 +1,5 @@ +name = "shallow" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_depth/shallow/manifest.toml b/test/testdata/workspace_depth/shallow/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_depth/shallow/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_depth/too_deep/nested/nested/project/gleam.toml b/test/testdata/workspace_depth/too_deep/nested/nested/project/gleam.toml new file mode 100644 index 0000000..2d9ea8b --- /dev/null +++ b/test/testdata/workspace_depth/too_deep/nested/nested/project/gleam.toml @@ -0,0 +1,5 @@ +name = "too_deep" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_depth/too_deep/nested/nested/project/manifest.toml b/test/testdata/workspace_depth/too_deep/nested/nested/project/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_depth/too_deep/nested/nested/project/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_depth_zero/container/project/gleam.toml b/test/testdata/workspace_depth_zero/container/project/gleam.toml new file mode 100644 index 0000000..551bc52 --- /dev/null +++ b/test/testdata/workspace_depth_zero/container/project/gleam.toml @@ -0,0 +1,5 @@ +name = "depth_zero" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_depth_zero/container/project/manifest.toml b/test/testdata/workspace_depth_zero/container/project/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_depth_zero/container/project/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_multi_level/gleam.toml b/test/testdata/workspace_multi_level/gleam.toml new file mode 100644 index 0000000..b425fd9 --- /dev/null +++ b/test/testdata/workspace_multi_level/gleam.toml @@ -0,0 +1,2 @@ +name = "root_not_project" +version = "1.0.0" diff --git a/test/testdata/workspace_multi_level/libs/common/gleam.toml b/test/testdata/workspace_multi_level/libs/common/gleam.toml new file mode 100644 index 0000000..2444aa4 --- /dev/null +++ b/test/testdata/workspace_multi_level/libs/common/gleam.toml @@ -0,0 +1,5 @@ +name = "common" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_multi_level/libs/common/manifest.toml b/test/testdata/workspace_multi_level/libs/common/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_multi_level/libs/common/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_multi_level/services/api/gleam.toml b/test/testdata/workspace_multi_level/services/api/gleam.toml new file mode 100644 index 0000000..6015359 --- /dev/null +++ b/test/testdata/workspace_multi_level/services/api/gleam.toml @@ -0,0 +1,5 @@ +name = "api" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_multi_level/services/api/manifest.toml b/test/testdata/workspace_multi_level/services/api/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_multi_level/services/api/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_multi_level/services/worker/gleam.toml b/test/testdata/workspace_multi_level/services/worker/gleam.toml new file mode 100644 index 0000000..7e92fb8 --- /dev/null +++ b/test/testdata/workspace_multi_level/services/worker/gleam.toml @@ -0,0 +1,5 @@ +name = "worker" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_multi_level/services/worker/manifest.toml b/test/testdata/workspace_multi_level/services/worker/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_multi_level/services/worker/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_partial/complete/gleam.toml b/test/testdata/workspace_partial/complete/gleam.toml new file mode 100644 index 0000000..00b172c --- /dev/null +++ b/test/testdata/workspace_partial/complete/gleam.toml @@ -0,0 +1,5 @@ +name = "complete" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_partial/complete/manifest.toml b/test/testdata/workspace_partial/complete/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_partial/complete/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_partial/only_gleam/gleam.toml b/test/testdata/workspace_partial/only_gleam/gleam.toml new file mode 100644 index 0000000..be4beda --- /dev/null +++ b/test/testdata/workspace_partial/only_gleam/gleam.toml @@ -0,0 +1,5 @@ +name = "only_gleam" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_partial/only_manifest/manifest.toml b/test/testdata/workspace_partial/only_manifest/manifest.toml new file mode 100644 index 0000000..e6fd74e --- /dev/null +++ b/test/testdata/workspace_partial/only_manifest/manifest.toml @@ -0,0 +1,3 @@ +packages = [] + +[requirements] diff --git a/test/testdata/workspace_root_project/gleam.toml b/test/testdata/workspace_root_project/gleam.toml new file mode 100644 index 0000000..6f3f25f --- /dev/null +++ b/test/testdata/workspace_root_project/gleam.toml @@ -0,0 +1,5 @@ +name = "root_app" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_root_project/manifest.toml b/test/testdata/workspace_root_project/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_root_project/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_root_project/packages/nested_app/gleam.toml b/test/testdata/workspace_root_project/packages/nested_app/gleam.toml new file mode 100644 index 0000000..f1c59da --- /dev/null +++ b/test/testdata/workspace_root_project/packages/nested_app/gleam.toml @@ -0,0 +1,5 @@ +name = "nested_app" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_root_project/packages/nested_app/manifest.toml b/test/testdata/workspace_root_project/packages/nested_app/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_root_project/packages/nested_app/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_skip/.hidden/nested/gleam.toml b/test/testdata/workspace_skip/.hidden/nested/gleam.toml new file mode 100644 index 0000000..ad630de --- /dev/null +++ b/test/testdata/workspace_skip/.hidden/nested/gleam.toml @@ -0,0 +1,5 @@ +name = "dot_hidden" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_skip/.hidden/nested/manifest.toml b/test/testdata/workspace_skip/.hidden/nested/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_skip/.hidden/nested/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_skip/build/hidden/gleam.toml b/test/testdata/workspace_skip/build/hidden/gleam.toml new file mode 100644 index 0000000..bc3f1cd --- /dev/null +++ b/test/testdata/workspace_skip/build/hidden/gleam.toml @@ -0,0 +1,5 @@ +name = "build_hidden" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_skip/build/hidden/manifest.toml b/test/testdata/workspace_skip/build/hidden/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_skip/build/hidden/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_skip/deps/hidden/gleam.toml b/test/testdata/workspace_skip/deps/hidden/gleam.toml new file mode 100644 index 0000000..11e1d1f --- /dev/null +++ b/test/testdata/workspace_skip/deps/hidden/gleam.toml @@ -0,0 +1,5 @@ +name = "deps_hidden" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_skip/deps/hidden/manifest.toml b/test/testdata/workspace_skip/deps/hidden/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_skip/deps/hidden/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/testdata/workspace_skip/visible/gleam.toml b/test/testdata/workspace_skip/visible/gleam.toml new file mode 100644 index 0000000..f98bb0b --- /dev/null +++ b/test/testdata/workspace_skip/visible/gleam.toml @@ -0,0 +1,5 @@ +name = "visible" +version = "1.0.0" + +[dependencies] +gleam_stdlib = ">= 1.0.0 and < 2.0.0" diff --git a/test/testdata/workspace_skip/visible/manifest.toml b/test/testdata/workspace_skip/visible/manifest.toml new file mode 100644 index 0000000..46aad1c --- /dev/null +++ b/test/testdata/workspace_skip/visible/manifest.toml @@ -0,0 +1,6 @@ +packages = [ + { name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DEADBEEF" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/test/warning_test.gleam b/test/warning_test.gleam index 4ecdc43..e1de03d 100644 --- a/test/warning_test.gleam +++ b/test/warning_test.gleam @@ -5,7 +5,8 @@ import gleam/option import gleam/string import gleamsver.{SemVer} import go_over/advisories/advisories.{Advisory} -import go_over/packages.{Package, PackageSourceHex} +import go_over/packages.{Package, PackageSourceGit, PackageSourceHex} +import go_over/sarif import go_over/warning.{type Warning} import go_over_test @@ -67,7 +68,6 @@ pub fn adv_to_warning_test() { )) } -// SOMETHING IS WRONG HERE pub fn retired_to_warning_test() { // with message { @@ -173,13 +173,76 @@ pub fn retired_to_warning_test() { } } -pub fn outdated_to_warning_test() { - let ver = "1.2.3" +pub fn sarif_format_test() { + let warning = + warning.adv_to_warning(example_package, [ + Advisory( + "GHSA-test-1234", + "package for warning tests", + "high", + [], + "example vulnerability", + ), + ]) + |> list.first + |> fn(r) { + case r { + Ok(w) -> w + Error(_) -> panic as "expected warning" + } + } + go_over_test.birdie_snap_with_input( + sarif.to_sarif_run("backend", [warning]) + |> json.to_string() + |> pprint_json(), + warning, + "warning_format_as_sarif@adv_to_warning", + ) +} + +pub fn info_to_warning_test() { + to_warning_format( + "info_to_warning", + "missing-package", + warning.info_to_warning( + "missing-package", + "Info: package 'missing-package' is not a dependency", + ), + ) +} + +pub fn git_deps_to_warnings_test() { to_warning_format( - "outdated_to_warning", - #(example_package, ver), - warning.outdated_to_warning(example_package, ver), + "git_deps_to_warnings", + "c", + warning.git_deps_to_warnings([ + Package("a", SemVer(1, 0, 0, "", ""), "1.0.0", True, PackageSourceHex), + Package("c", SemVer(0, 1, 0, "", ""), "0.1.0", False, PackageSourceGit), + ]) + |> list.first + |> fn(r) { + case r { + Ok(w) -> w + Error(_) -> panic as "expected warning" + } + }, + ) +} + +pub fn sarif_includes_info_warnings_test() { + let info = + warning.info_to_warning( + "missing-package", + "Info: package 'missing-package' is not a dependency", + ) + + go_over_test.birdie_snap_with_input( + sarif.to_sarif_run("backend", [info]) + |> json.to_string() + |> pprint_json(), + info, + "warning_format_as_sarif@info_to_warning", ) } @@ -206,14 +269,13 @@ pub fn severity_as_string_test() { "something", )) == "package-retired:something" - assert warning.severity_as_string(warning.SeverityPackageOutdated) - == "package-outdated" assert warning.severity_as_string(warning.SeverityRejectedLicense) == "rejected-license" assert warning.severity_as_string(warning.SeverityCritical) == "critical" assert warning.severity_as_string(warning.SeverityHigh) == "high" assert warning.severity_as_string(warning.SeverityLow) == "low" assert warning.severity_as_string(warning.SeverityModerate) == "moderate" + assert warning.severity_as_string(warning.SeverityInfo) == "info" assert warning.severity_as_string(warning.SeverityUnknown("something")) == "unknown-something" } @@ -229,8 +291,7 @@ pub fn string_to_severity_test() { == warning.SeverityPackageRetiredRenamed assert warning.string_to_severity("package-retired:something") == warning.SeverityPackageRetiredOtherReason("something") - assert warning.string_to_severity("package-outdated") - == warning.SeverityPackageOutdated + assert warning.string_to_severity("info") == warning.SeverityInfo assert warning.string_to_severity("rejected-license") == warning.SeverityRejectedLicense assert warning.string_to_severity("critical") == warning.SeverityCritical diff --git a/test/workspace_test.gleam b/test/workspace_test.gleam new file mode 100644 index 0000000..4985f8e --- /dev/null +++ b/test/workspace_test.gleam @@ -0,0 +1,256 @@ +import gleam/list +import gleam/order +import gleam/string +import go_over/workspace + +const workspace = "test/testdata/workspace" + +const workspace_depth = "test/testdata/workspace_depth" + +const workspace_root_project = "test/testdata/workspace_root_project" + +const workspace_partial = "test/testdata/workspace_partial" + +const workspace_skip = "test/testdata/workspace_skip" + +const workspace_depth_zero = "test/testdata/workspace_depth_zero" + +const workspace_multi_level = "test/testdata/workspace_multi_level" + +pub fn discover_workspace_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace, 3) + + assert projects + == [ + workspace <> "/app_a", + workspace <> "/app_b", + ] + assert skipped == [] +} + +pub fn discover_empty_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover("test/testdata/gleam", 3) + + assert projects == [] + assert skipped == [] +} + +pub fn discover_max_depth_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_depth, 3) + + assert projects + == [ + workspace_depth <> "/at_max/nested/project", + workspace_depth <> "/shallow", + ] + + assert skipped == [workspace_depth <> "/too_deep/nested/nested/project"] +} + +pub fn discover_or_error_empty_test() { + let assert Error(msg) = workspace.discover_or_error("test/testdata/gleam", 3) + + assert msg == "no gleam projects found under test/testdata/gleam" +} + +pub fn discover_or_error_success_test() { + let assert Ok(workspace.DiscoverResult(projects, skipped)) = + workspace.discover_or_error(workspace, 3) + + assert list.length(projects) == 2 + assert skipped == [] +} + +pub fn discover_root_is_project_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_root_project, 3) + + assert projects == [workspace_root_project] + assert skipped == [] +} + +pub fn discover_partial_projects_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_partial, 3) + + assert projects == [workspace_partial <> "/complete"] + assert skipped == [] +} + +pub fn discover_skips_ignored_dirs_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_skip, 3) + + assert projects == [workspace_skip <> "/visible"] + assert skipped == [] +} + +pub fn discover_sorts_projects_test() { + let workspace.DiscoverResult(projects, _) = workspace.discover(workspace, 3) + + assert projects + == list.sort(projects, fn(a, b) { + case string.compare(a, b) { + order.Eq -> order.Eq + order.Lt -> order.Lt + order.Gt -> order.Gt + } + }) +} + +pub fn discover_max_depth_zero_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_depth_zero, 0) + + assert projects == [] + assert skipped == [workspace_depth_zero <> "/container/project"] +} + +pub fn discover_max_depth_one_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_depth, 1) + + assert projects == [workspace_depth <> "/shallow"] + assert skipped + == [ + workspace_depth <> "/at_max/nested/project", + workspace_depth <> "/too_deep/nested/nested/project", + ] +} + +pub fn discover_nonexistent_path_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover("test/testdata/does_not_exist", 3) + + assert projects == [] + assert skipped == [] +} + +pub fn discover_scan_root_is_project_ignores_max_depth_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_root_project, 0) + + assert projects == [workspace_root_project] + assert skipped == [] +} + +pub fn discover_or_error_root_is_project_test() { + let assert Ok(workspace.DiscoverResult(projects, skipped)) = + workspace.discover_or_error(workspace_root_project, 3) + + assert projects == [workspace_root_project] + assert skipped == [] +} + +pub fn discover_root_does_not_descend_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_root_project, 3) + + assert projects == [workspace_root_project] + assert skipped == [] + assert !list.contains( + projects, + workspace_root_project <> "/packages/nested_app", + ) +} + +pub fn discover_multi_level_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_multi_level, 3) + + assert projects + == [ + workspace_multi_level <> "/libs/common", + workspace_multi_level <> "/services/api", + workspace_multi_level <> "/services/worker", + ] + assert skipped == [] +} + +pub fn discover_partial_only_gleam_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_partial <> "/only_gleam", 3) + + assert projects == [] + assert skipped == [] +} + +pub fn discover_partial_only_manifest_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_partial <> "/only_manifest", 3) + + assert projects == [] + assert skipped == [] +} + +pub fn discover_skipped_sorted_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_depth, 1) + + assert projects == [workspace_depth <> "/shallow"] + assert skipped + == [ + workspace_depth <> "/at_max/nested/project", + workspace_depth <> "/too_deep/nested/nested/project", + ] + assert skipped + == list.sort(skipped, fn(a, b) { + case string.compare(a, b) { + order.Eq -> order.Eq + order.Lt -> order.Lt + order.Gt -> order.Gt + } + }) +} + +pub fn discover_skips_node_modules_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_skip, 3) + + assert projects == [workspace_skip <> "/visible"] + assert skipped == [] + assert !list.contains(projects, workspace_skip <> "/node_modules/hidden") +} + +pub fn discover_skips_go_over_dir_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_skip, 3) + + assert projects == [workspace_skip <> "/visible"] + assert skipped == [] + assert !list.contains(projects, workspace_skip <> "/.go-over/hidden") +} + +pub fn discover_max_depth_two_test() { + let workspace.DiscoverResult(projects, skipped) = + workspace.discover(workspace_depth, 2) + + assert projects == [workspace_depth <> "/shallow"] + assert skipped + == [ + workspace_depth <> "/at_max/nested/project", + workspace_depth <> "/too_deep/nested/nested/project", + ] +} + +pub fn discover_or_error_nonexistent_test() { + let assert Error(msg) = + workspace.discover_or_error("test/testdata/does_not_exist", 3) + + assert msg == "no gleam projects found under test/testdata/does_not_exist" +} + +pub fn discover_or_error_depth_zero_empty_test() { + let assert Error(msg) = workspace.discover_or_error(workspace_depth_zero, 0) + + assert msg == "no gleam projects found under " <> workspace_depth_zero +} + +pub fn discover_or_error_skipped_only_test() { + let assert Error(msg) = workspace.discover_or_error(workspace_depth, 0) + + assert msg == "no gleam projects found under " <> workspace_depth +}