pr-checkmate 3.0.4 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{cli.js → cjs/cli.js} +30 -30
- package/dist/{config → cjs/config}/index.d.ts +2 -2
- package/dist/{config → cjs/config}/index.js +8 -7
- package/dist/{config → cjs/config}/loader.d.ts +1 -1
- package/dist/{config → cjs/config}/loader.js +8 -8
- package/dist/{core → cjs/core}/checks/dependencies/banned-imports.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/dependencies/banned-imports.js +10 -10
- package/dist/{core → cjs/core}/checks/dependencies/circular-deps.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/dependencies/circular-deps.js +27 -27
- package/dist/{core → cjs/core}/checks/dependencies/dependency-check.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/dependencies/dependency-check.js +17 -17
- package/dist/{core → cjs/core}/checks/dependencies/grype-scan.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/dependencies/grype-scan.js +18 -18
- package/dist/{core → cjs/core}/checks/dependencies/license-check.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/dependencies/license-check.js +12 -12
- package/dist/{core → cjs/core}/checks/dependencies/npm-audit.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/dependencies/npm-audit.js +14 -14
- package/dist/{core → cjs/core}/checks/dependencies/outdated-deps.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/dependencies/outdated-deps.js +14 -14
- package/dist/{core → cjs/core}/checks/dependencies/vuln-scan.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/dependencies/vuln-scan.js +14 -14
- package/dist/{core → cjs/core}/checks/findings.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/findings.js +16 -16
- package/dist/{core → cjs/core}/checks/git/custom-rules.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/git/custom-rules.js +19 -19
- package/dist/{core → cjs/core}/checks/git/diff-smell.d.ts +2 -2
- package/dist/{core → cjs/core}/checks/git/diff-smell.js +13 -13
- package/dist/{core → cjs/core}/checks/git/large-files.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/git/large-files.js +15 -15
- package/dist/{core → cjs/core}/checks/git/leftover-debug.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/git/leftover-debug.js +13 -13
- package/dist/{core → cjs/core}/checks/git/lockfile-drift.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/git/lockfile-drift.js +12 -12
- package/dist/{core → cjs/core}/checks/git/merge-conflict.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/git/merge-conflict.js +15 -15
- package/dist/{core → cjs/core}/checks/git/missing-tests.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/git/missing-tests.js +15 -15
- package/dist/{core → cjs/core}/checks/git/todo-fixme.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/git/todo-fixme.js +11 -11
- package/dist/{core → cjs/core}/checks/languages/cpp-format.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/languages/cpp-format.js +18 -18
- package/dist/{core → cjs/core}/checks/languages/external-checks.d.ts +9 -9
- package/dist/{core → cjs/core}/checks/languages/external-checks.js +10 -10
- package/dist/{core → cjs/core}/checks/languages/external-runner.d.ts +2 -2
- package/dist/{core → cjs/core}/checks/languages/external-runner.js +21 -21
- package/dist/{core → cjs/core}/checks/languages/go-format.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/languages/go-format.js +21 -21
- package/dist/cjs/core/checks/languages/lint.d.ts +2 -0
- package/dist/{core → cjs/core}/checks/languages/lint.js +21 -21
- package/dist/{core → cjs/core}/checks/languages/prettier.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/languages/prettier.js +18 -18
- package/dist/{core → cjs/core}/checks/languages/python-format.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/languages/python-format.js +14 -14
- package/dist/{core → cjs/core}/checks/languages/python-lint.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/languages/python-lint.js +13 -13
- package/dist/{core → cjs/core}/checks/languages/python-typecheck.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/languages/python-typecheck.js +29 -29
- package/dist/{core → cjs/core}/checks/languages/typecheck.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/languages/typecheck.js +13 -13
- package/dist/{core → cjs/core}/checks/pr/commitlint.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/pr/commitlint.js +12 -12
- package/dist/{core → cjs/core}/checks/pr/pr-body.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/pr/pr-body.js +8 -8
- package/dist/{core → cjs/core}/checks/pr/pr-size-check.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/pr/pr-size-check.js +10 -10
- package/dist/{core → cjs/core}/checks/pr/pr-title-lint.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/pr/pr-title-lint.js +8 -8
- package/dist/{core → cjs/core}/checks/quality/case-collision.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/case-collision.js +7 -7
- package/dist/{core → cjs/core}/checks/quality/config-validation.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/config-validation.js +9 -9
- package/dist/{core → cjs/core}/checks/quality/coverage.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/coverage.js +4 -4
- package/dist/{core → cjs/core}/checks/quality/dead-code.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/dead-code.js +23 -23
- package/dist/{core → cjs/core}/checks/quality/duplicate-check.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/duplicate-check.js +22 -22
- package/dist/{core → cjs/core}/checks/quality/license-header.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/license-header.js +16 -16
- package/dist/{core → cjs/core}/checks/quality/markdown-lint.d.ts +1 -1
- package/dist/cjs/core/checks/quality/markdown-lint.js +147 -0
- package/dist/{core → cjs/core}/checks/quality/package-size.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/package-size.js +12 -12
- package/dist/{core → cjs/core}/checks/quality/spellcheck.d.ts +2 -2
- package/dist/{core → cjs/core}/checks/quality/spellcheck.js +18 -18
- package/dist/{core → cjs/core}/checks/quality/symlinks.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/symlinks.js +8 -8
- package/dist/{core → cjs/core}/checks/quality/yaml-lint.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/quality/yaml-lint.js +12 -12
- package/dist/{core → cjs/core}/checks/security/diff-security.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/security/diff-security.js +15 -15
- package/dist/{core → cjs/core}/checks/security/dockerfile-security.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/security/dockerfile-security.js +16 -16
- package/dist/{core → cjs/core}/checks/security/migration-safety.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/security/migration-safety.js +17 -17
- package/dist/{core → cjs/core}/checks/security/security.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/security/security.js +21 -21
- package/dist/{core → cjs/core}/checks/security/sensitive-file-guard.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/security/sensitive-file-guard.js +14 -14
- package/dist/{core → cjs/core}/checks/security/workflow-security.d.ts +2 -2
- package/dist/{core → cjs/core}/checks/security/workflow-security.js +14 -14
- package/dist/{core → cjs/core}/checks/tool-install.d.ts +1 -1
- package/dist/{core → cjs/core}/checks/tool-install.js +2 -2
- package/dist/{core → cjs/core}/context.d.ts +1 -1
- package/dist/{core → cjs/core}/context.js +16 -16
- package/dist/cjs/core/index.d.ts +4 -0
- package/dist/{core → cjs/core}/index.js +7 -7
- package/dist/{core → cjs/core}/install/cache.js +2 -2
- package/dist/{core → cjs/core}/install/doctor.d.ts +4 -4
- package/dist/{core → cjs/core}/install/doctor.js +34 -34
- package/dist/{core → cjs/core}/install/ensure.d.ts +2 -2
- package/dist/{core → cjs/core}/install/ensure.js +15 -14
- package/dist/{core → cjs/core}/install/lock.js +4 -4
- package/dist/{core → cjs/core}/install/prepare.d.ts +4 -4
- package/dist/{core → cjs/core}/install/prepare.js +13 -13
- package/dist/{core → cjs/core}/install/profiles.d.ts +2 -2
- package/dist/{core → cjs/core}/install/profiles.js +1 -1
- package/dist/{core → cjs/core}/install/reuse.d.ts +1 -1
- package/dist/{core → cjs/core}/install/reuse.js +4 -4
- package/dist/{core → cjs/core}/registry.d.ts +1 -1
- package/dist/cjs/core/registry.js +112 -0
- package/dist/{core → cjs/core}/reporters.d.ts +1 -1
- package/dist/{core → cjs/core}/reporters.js +28 -28
- package/dist/{core → cjs/core}/run-checks.d.ts +2 -2
- package/dist/{core → cjs/core}/run-checks.js +31 -31
- package/dist/{core → cjs/core}/targets.d.ts +1 -1
- package/dist/{core → cjs/core}/targets.js +20 -20
- package/dist/{core → cjs/core}/types.d.ts +3 -3
- package/dist/cjs/index.d.ts +12 -0
- package/dist/{index.js → cjs/index.js} +18 -18
- package/dist/{init.js → cjs/init.js} +68 -56
- package/dist/{interfaces → cjs/interfaces}/index.d.ts +3 -3
- package/dist/cjs/package.json +1 -0
- package/dist/{scripts → cjs/scripts}/copy-configs.js +22 -7
- package/dist/{scripts → cjs/scripts}/generate-install-manifests.js +2 -2
- package/dist/{scripts → cjs/scripts}/publish-github.js +4 -4
- package/dist/{utils → cjs/utils}/detect-language.js +12 -12
- package/dist/{utils → cjs/utils}/git.js +2 -2
- package/dist/{utils → cjs/utils}/http.js +2 -2
- package/dist/cjs/utils/index.d.ts +15 -0
- package/dist/{utils → cjs/utils}/index.js +13 -13
- package/dist/{utils → cjs/utils}/own-deps.d.ts +2 -0
- package/dist/{utils → cjs/utils}/own-deps.js +1 -0
- package/dist/{utils → cjs/utils}/package-version.js +2 -1
- package/dist/{utils → cjs/utils}/resolve-bin.js +2 -1
- package/dist/{utils → cjs/utils}/update-notifier.js +4 -4
- package/dist/esm/cli.d.ts +2 -0
- package/dist/esm/cli.js +137 -0
- package/dist/esm/config/constants.d.ts +153 -0
- package/dist/esm/config/constants.js +360 -0
- package/dist/esm/config/index.d.ts +40 -0
- package/dist/esm/config/index.js +60 -0
- package/dist/esm/config/loader.d.ts +6 -0
- package/dist/esm/config/loader.js +191 -0
- package/dist/esm/config/pr-checkmate-workflow-docker.yml +79 -0
- package/dist/esm/config/pr-checkmate-workflow.yml +92 -0
- package/dist/esm/core/checks/dependencies/banned-imports.d.ts +2 -0
- package/dist/esm/core/checks/dependencies/banned-imports.js +70 -0
- package/dist/esm/core/checks/dependencies/circular-deps.d.ts +2 -0
- package/dist/esm/core/checks/dependencies/circular-deps.js +155 -0
- package/dist/esm/core/checks/dependencies/dependency-check.d.ts +43 -0
- package/dist/esm/core/checks/dependencies/dependency-check.js +174 -0
- package/dist/esm/core/checks/dependencies/grype-scan.d.ts +22 -0
- package/dist/esm/core/checks/dependencies/grype-scan.js +112 -0
- package/dist/esm/core/checks/dependencies/license-check.d.ts +2 -0
- package/dist/esm/core/checks/dependencies/license-check.js +61 -0
- package/dist/esm/core/checks/dependencies/npm-audit.d.ts +6 -0
- package/dist/esm/core/checks/dependencies/npm-audit.js +80 -0
- package/dist/esm/core/checks/dependencies/outdated-deps.d.ts +2 -0
- package/dist/esm/core/checks/dependencies/outdated-deps.js +66 -0
- package/dist/esm/core/checks/dependencies/vuln-scan.d.ts +2 -0
- package/dist/esm/core/checks/dependencies/vuln-scan.js +54 -0
- package/dist/esm/core/checks/findings.d.ts +299 -0
- package/dist/esm/core/checks/findings.js +701 -0
- package/dist/esm/core/checks/git/custom-rules.d.ts +2 -0
- package/dist/esm/core/checks/git/custom-rules.js +106 -0
- package/dist/esm/core/checks/git/diff-smell.d.ts +19 -0
- package/dist/esm/core/checks/git/diff-smell.js +69 -0
- package/dist/esm/core/checks/git/large-files.d.ts +2 -0
- package/dist/esm/core/checks/git/large-files.js +112 -0
- package/dist/esm/core/checks/git/leftover-debug.d.ts +2 -0
- package/dist/esm/core/checks/git/leftover-debug.js +89 -0
- package/dist/esm/core/checks/git/lockfile-drift.d.ts +2 -0
- package/dist/esm/core/checks/git/lockfile-drift.js +114 -0
- package/dist/esm/core/checks/git/merge-conflict.d.ts +2 -0
- package/dist/esm/core/checks/git/merge-conflict.js +117 -0
- package/dist/esm/core/checks/git/missing-tests.d.ts +2 -0
- package/dist/esm/core/checks/git/missing-tests.js +105 -0
- package/dist/esm/core/checks/git/todo-fixme.d.ts +7 -0
- package/dist/esm/core/checks/git/todo-fixme.js +80 -0
- package/dist/esm/core/checks/languages/cpp-format.d.ts +7 -0
- package/dist/esm/core/checks/languages/cpp-format.js +91 -0
- package/dist/esm/core/checks/languages/external-checks.d.ts +77 -0
- package/dist/esm/core/checks/languages/external-checks.js +197 -0
- package/dist/esm/core/checks/languages/external-runner.d.ts +119 -0
- package/dist/esm/core/checks/languages/external-runner.js +138 -0
- package/dist/esm/core/checks/languages/go-format.d.ts +8 -0
- package/dist/esm/core/checks/languages/go-format.js +163 -0
- package/dist/esm/core/checks/languages/lint.d.ts +2 -0
- package/dist/esm/core/checks/languages/lint.js +92 -0
- package/dist/esm/core/checks/languages/prettier.d.ts +2 -0
- package/dist/esm/core/checks/languages/prettier.js +85 -0
- package/dist/esm/core/checks/languages/python-format.d.ts +7 -0
- package/dist/esm/core/checks/languages/python-format.js +61 -0
- package/dist/esm/core/checks/languages/python-lint.d.ts +9 -0
- package/dist/esm/core/checks/languages/python-lint.js +55 -0
- package/dist/esm/core/checks/languages/python-typecheck.d.ts +14 -0
- package/dist/esm/core/checks/languages/python-typecheck.js +184 -0
- package/dist/esm/core/checks/languages/ruff-shared.d.ts +10 -0
- package/dist/esm/core/checks/languages/ruff-shared.js +35 -0
- package/dist/esm/core/checks/languages/typecheck.d.ts +2 -0
- package/dist/esm/core/checks/languages/typecheck.js +61 -0
- package/dist/esm/core/checks/pr/commitlint.d.ts +2 -0
- package/dist/esm/core/checks/pr/commitlint.js +102 -0
- package/dist/esm/core/checks/pr/pr-body.d.ts +2 -0
- package/dist/esm/core/checks/pr/pr-body.js +43 -0
- package/dist/esm/core/checks/pr/pr-size-check.d.ts +2 -0
- package/dist/esm/core/checks/pr/pr-size-check.js +42 -0
- package/dist/esm/core/checks/pr/pr-title-lint.d.ts +2 -0
- package/dist/esm/core/checks/pr/pr-title-lint.js +31 -0
- package/dist/esm/core/checks/quality/case-collision.d.ts +8 -0
- package/dist/esm/core/checks/quality/case-collision.js +39 -0
- package/dist/esm/core/checks/quality/config-validation.d.ts +11 -0
- package/dist/esm/core/checks/quality/config-validation.js +188 -0
- package/dist/esm/core/checks/quality/coverage.d.ts +2 -0
- package/dist/esm/core/checks/quality/coverage.js +62 -0
- package/dist/esm/core/checks/quality/dead-code.d.ts +2 -0
- package/dist/esm/core/checks/quality/dead-code.js +124 -0
- package/dist/esm/core/checks/quality/duplicate-check.d.ts +2 -0
- package/dist/esm/core/checks/quality/duplicate-check.js +141 -0
- package/dist/esm/core/checks/quality/license-header.d.ts +7 -0
- package/dist/esm/core/checks/quality/license-header.js +81 -0
- package/dist/esm/core/checks/quality/markdown-lint.d.ts +2 -0
- package/dist/esm/core/checks/quality/markdown-lint.js +141 -0
- package/dist/esm/core/checks/quality/package-size.d.ts +36 -0
- package/dist/esm/core/checks/quality/package-size.js +196 -0
- package/dist/esm/core/checks/quality/spellcheck.d.ts +77 -0
- package/dist/esm/core/checks/quality/spellcheck.js +296 -0
- package/dist/esm/core/checks/quality/symlinks.d.ts +16 -0
- package/dist/esm/core/checks/quality/symlinks.js +57 -0
- package/dist/esm/core/checks/quality/yaml-lint.d.ts +20 -0
- package/dist/esm/core/checks/quality/yaml-lint.js +116 -0
- package/dist/esm/core/checks/security/diff-security.d.ts +2 -0
- package/dist/esm/core/checks/security/diff-security.js +354 -0
- package/dist/esm/core/checks/security/dockerfile-security.d.ts +2 -0
- package/dist/esm/core/checks/security/dockerfile-security.js +98 -0
- package/dist/esm/core/checks/security/migration-safety.d.ts +2 -0
- package/dist/esm/core/checks/security/migration-safety.js +137 -0
- package/dist/esm/core/checks/security/security.d.ts +63 -0
- package/dist/esm/core/checks/security/security.js +201 -0
- package/dist/esm/core/checks/security/sensitive-file-guard.d.ts +2 -0
- package/dist/esm/core/checks/security/sensitive-file-guard.js +160 -0
- package/dist/esm/core/checks/security/workflow-security.d.ts +12 -0
- package/dist/esm/core/checks/security/workflow-security.js +100 -0
- package/dist/esm/core/checks/tool-install.d.ts +102 -0
- package/dist/esm/core/checks/tool-install.js +66 -0
- package/dist/esm/core/commands.d.ts +27 -0
- package/dist/esm/core/commands.js +89 -0
- package/dist/esm/core/context.d.ts +62 -0
- package/dist/esm/core/context.js +241 -0
- package/dist/esm/core/index.d.ts +4 -0
- package/dist/esm/core/index.js +4 -0
- package/dist/esm/core/install/cache.d.ts +58 -0
- package/dist/esm/core/install/cache.js +142 -0
- package/dist/esm/core/install/doctor.d.ts +35 -0
- package/dist/esm/core/install/doctor.js +164 -0
- package/dist/esm/core/install/ensure.d.ts +77 -0
- package/dist/esm/core/install/ensure.js +269 -0
- package/dist/esm/core/install/lock.d.ts +34 -0
- package/dist/esm/core/install/lock.js +92 -0
- package/dist/esm/core/install/merge.d.ts +47 -0
- package/dist/esm/core/install/merge.js +85 -0
- package/dist/esm/core/install/prepare.d.ts +76 -0
- package/dist/esm/core/install/prepare.js +142 -0
- package/dist/esm/core/install/profiles.d.ts +78 -0
- package/dist/esm/core/install/profiles.js +192 -0
- package/dist/esm/core/install/reuse.d.ts +41 -0
- package/dist/esm/core/install/reuse.js +100 -0
- package/dist/esm/core/registry.d.ts +6 -0
- package/dist/esm/core/registry.js +109 -0
- package/dist/esm/core/reporters.d.ts +18 -0
- package/dist/esm/core/reporters.js +266 -0
- package/dist/esm/core/run-checks.d.ts +33 -0
- package/dist/esm/core/run-checks.js +293 -0
- package/dist/esm/core/targets.d.ts +140 -0
- package/dist/esm/core/targets.js +213 -0
- package/dist/esm/core/types.d.ts +195 -0
- package/dist/esm/core/types.js +38 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/init.d.ts +38 -0
- package/dist/esm/init.js +421 -0
- package/dist/esm/interfaces/index.d.ts +432 -0
- package/dist/esm/interfaces/index.js +2 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/scripts/copy-configs.d.ts +1 -0
- package/dist/esm/scripts/copy-configs.js +64 -0
- package/dist/esm/scripts/generate-install-manifests.d.ts +39 -0
- package/dist/esm/scripts/generate-install-manifests.js +115 -0
- package/dist/esm/scripts/publish-github.d.ts +1 -0
- package/dist/esm/scripts/publish-github.js +45 -0
- package/dist/esm/utils/ci.d.ts +11 -0
- package/dist/esm/utils/ci.js +40 -0
- package/dist/esm/utils/detect-language.d.ts +48 -0
- package/dist/esm/utils/detect-language.js +234 -0
- package/dist/esm/utils/direct-deps.d.ts +21 -0
- package/dist/esm/utils/direct-deps.js +220 -0
- package/dist/esm/utils/execa.d.ts +22 -0
- package/dist/esm/utils/execa.js +10 -0
- package/dist/esm/utils/git.d.ts +14 -0
- package/dist/esm/utils/git.js +21 -0
- package/dist/esm/utils/glob.d.ts +24 -0
- package/dist/esm/utils/glob.js +40 -0
- package/dist/esm/utils/http.d.ts +59 -0
- package/dist/esm/utils/http.js +115 -0
- package/dist/esm/utils/index.d.ts +15 -0
- package/dist/esm/utils/index.js +26 -0
- package/dist/esm/utils/logger.d.ts +65 -0
- package/dist/esm/utils/logger.js +157 -0
- package/dist/esm/utils/own-deps.d.ts +16 -0
- package/dist/esm/utils/own-deps.js +107 -0
- package/dist/esm/utils/package-version.d.ts +4 -0
- package/dist/esm/utils/package-version.js +38 -0
- package/dist/esm/utils/pool.d.ts +49 -0
- package/dist/esm/utils/pool.js +113 -0
- package/dist/esm/utils/resolve-bin.d.ts +67 -0
- package/dist/esm/utils/resolve-bin.js +156 -0
- package/dist/esm/utils/result-collector.d.ts +33 -0
- package/dist/esm/utils/result-collector.js +44 -0
- package/dist/esm/utils/update-notifier.d.ts +19 -0
- package/dist/esm/utils/update-notifier.js +108 -0
- package/install-manifests/core/package-lock.json +4 -4
- package/install-manifests/core/package.json +1 -1
- package/install-manifests/duplicate/package-lock.json +0 -12
- package/install-manifests/js/package-lock.json +17 -41
- package/install-manifests/js/package.json +1 -1
- package/install-manifests/markdown/package-lock.json +1 -492
- package/install-manifests/markdown/package.json +1 -1
- package/package.json +17 -15
- package/dist/core/checks/languages/lint.d.ts +0 -2
- package/dist/core/checks/quality/markdown-lint.js +0 -110
- package/dist/core/index.d.ts +0 -4
- package/dist/core/registry.js +0 -112
- package/dist/index.d.ts +0 -12
- package/dist/utils/index.d.ts +0 -15
- /package/dist/{cli.d.ts → cjs/cli.d.ts} +0 -0
- /package/dist/{config → cjs/config}/constants.d.ts +0 -0
- /package/dist/{config → cjs/config}/constants.js +0 -0
- /package/dist/{config → cjs/config}/pr-checkmate-workflow-docker.yml +0 -0
- /package/dist/{config → cjs/config}/pr-checkmate-workflow.yml +0 -0
- /package/dist/{core → cjs/core}/checks/languages/ruff-shared.d.ts +0 -0
- /package/dist/{core → cjs/core}/checks/languages/ruff-shared.js +0 -0
- /package/dist/{core → cjs/core}/commands.d.ts +0 -0
- /package/dist/{core → cjs/core}/commands.js +0 -0
- /package/dist/{core → cjs/core}/install/cache.d.ts +0 -0
- /package/dist/{core → cjs/core}/install/lock.d.ts +0 -0
- /package/dist/{core → cjs/core}/install/merge.d.ts +0 -0
- /package/dist/{core → cjs/core}/install/merge.js +0 -0
- /package/dist/{core → cjs/core}/types.js +0 -0
- /package/dist/{init.d.ts → cjs/init.d.ts} +0 -0
- /package/dist/{interfaces → cjs/interfaces}/index.js +0 -0
- /package/dist/{scripts → cjs/scripts}/copy-configs.d.ts +0 -0
- /package/dist/{scripts → cjs/scripts}/generate-install-manifests.d.ts +0 -0
- /package/dist/{scripts → cjs/scripts}/publish-github.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/ci.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/ci.js +0 -0
- /package/dist/{utils → cjs/utils}/detect-language.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/direct-deps.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/direct-deps.js +0 -0
- /package/dist/{utils → cjs/utils}/execa.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/execa.js +0 -0
- /package/dist/{utils → cjs/utils}/git.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/glob.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/glob.js +0 -0
- /package/dist/{utils → cjs/utils}/http.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/logger.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/logger.js +0 -0
- /package/dist/{utils → cjs/utils}/package-version.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/pool.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/pool.js +0 -0
- /package/dist/{utils → cjs/utils}/resolve-bin.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/result-collector.d.ts +0 -0
- /package/dist/{utils → cjs/utils}/result-collector.js +0 -0
- /package/dist/{utils → cjs/utils}/update-notifier.d.ts +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getSourcePaths } from '../../../config/index.js';
|
|
4
|
+
import { execa } from '../../../utils/execa.js';
|
|
5
|
+
import { cleanupTempFile, logger, matchesAnyGlob, resolvePackageBin, } from '../../../utils/index.js';
|
|
6
|
+
import { clientToolPaths } from '../../install/reuse.js';
|
|
7
|
+
import { listTrackedFiles } from '../../targets.js';
|
|
8
|
+
import { pass, skip } from '../../types.js';
|
|
9
|
+
import { severityEmitter } from '../findings.js';
|
|
10
|
+
/**
|
|
11
|
+
* Cap for the analysis. dpdm builds the whole module graph, which on a large repo
|
|
12
|
+
* is the slowest part of a run — an uncapped analysis looks like a hung commit.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 120000;
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling for the assembled argument list. Passing every tracked source file as a
|
|
17
|
+
* separate argument overflows the OS argument limit (`E2BIG`) on a large repo, and
|
|
18
|
+
* a spawn that never starts writes no report. Past the ceiling dpdm gets globs
|
|
19
|
+
* instead — it expands them itself, over the same source paths.
|
|
20
|
+
*/
|
|
21
|
+
const MAX_ARGV_CHARS = 100000;
|
|
22
|
+
/** Extensions dpdm resolves for us; also the globs used in the fallback. */
|
|
23
|
+
const SOURCE_GLOBS = ['*.ts', '*.tsx', '*.js', '*.jsx'];
|
|
24
|
+
/**
|
|
25
|
+
* Resolve dpdm, preferring the consumer project's own copy (so a repo pinned to a
|
|
26
|
+
* different major keeps its own behaviour) and falling back to the copy bundled
|
|
27
|
+
* with pr-checkmate — the order `lint` and `typecheck` use for eslint/tsc.
|
|
28
|
+
*
|
|
29
|
+
* Returns null instead of throwing: an unresolvable analyzer is a skip, never a
|
|
30
|
+
* failed PR. This check used to shell out to `npx dpdm`, which silently fetched
|
|
31
|
+
* dpdm from the registry on first use — and when that fetch could not happen
|
|
32
|
+
* (offline or locked-down CI, private registry, exit 127) nothing was analyzed.
|
|
33
|
+
*/
|
|
34
|
+
function resolveDpdm(ctx) {
|
|
35
|
+
// No configuration of its own — dpdm reads the module graph — so the version
|
|
36
|
+
// check is the right question here and `clientOwnsConfig` stays off.
|
|
37
|
+
for (const options of [{ paths: clientToolPaths('dpdm', ctx) }, undefined]) {
|
|
38
|
+
try {
|
|
39
|
+
return resolvePackageBin('dpdm', 'lib/bin/dpdm.js', options);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Try the next location; both failing is a skip, handled by the caller.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the file arguments for dpdm, falling back to globs when the explicit list
|
|
49
|
+
* would overflow the OS argument limit. `sourcePaths` scopes the globs the same way
|
|
50
|
+
* `listTrackedFiles` scopes the explicit list.
|
|
51
|
+
*/
|
|
52
|
+
function toFileArgs(files, sourcePaths) {
|
|
53
|
+
const explicitLength = files.reduce((sum, file) => sum + file.length + 1, 0);
|
|
54
|
+
if (explicitLength <= MAX_ARGV_CHARS)
|
|
55
|
+
return files;
|
|
56
|
+
const roots = sourcePaths.length > 0 ? sourcePaths : ['.'];
|
|
57
|
+
const globs = roots.flatMap(root => SOURCE_GLOBS.map(ext => `${root.replace(/\/+$/, '')}/**/${ext}`));
|
|
58
|
+
logger.info(`[Circular Deps]: ${files.length} files exceed the argument limit — analyzing via globs (${globs.join(', ')})`);
|
|
59
|
+
return globs;
|
|
60
|
+
}
|
|
61
|
+
/** Read dpdm's report file; null when it is absent or unparseable. */
|
|
62
|
+
function readReport(reportPath) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(fs.readFileSync(reportPath, 'utf-8'));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export const circularDepsCheck = {
|
|
71
|
+
name: 'Circular Deps',
|
|
72
|
+
phase: 'blocking',
|
|
73
|
+
weight: 'heavy',
|
|
74
|
+
applies(ctx) {
|
|
75
|
+
if (ctx.config.circularDeps?.enabled === false)
|
|
76
|
+
return false;
|
|
77
|
+
return ctx.languages.includes('typescript') || ctx.languages.includes('javascript');
|
|
78
|
+
},
|
|
79
|
+
async run(ctx) {
|
|
80
|
+
const allFiles = await listTrackedFiles(ctx, SOURCE_GLOBS, getSourcePaths(ctx.cwd));
|
|
81
|
+
// A cycle detector needs the WHOLE graph, so an unreadable file list is the
|
|
82
|
+
// one case where passing would be worst: half a graph produces confident
|
|
83
|
+
// nonsense, and no graph reported as `pass` says the repository is acyclic
|
|
84
|
+
// when nobody looked (ticket 072).
|
|
85
|
+
if (allFiles === null)
|
|
86
|
+
return skip('git unavailable');
|
|
87
|
+
if (allFiles.length === 0)
|
|
88
|
+
return pass();
|
|
89
|
+
// Files the client exempted (generated code, vendored bundles) are dropped
|
|
90
|
+
// before analysis — and again from the cycles below, since the graph can reach
|
|
91
|
+
// an ignored file by following imports from one that is analyzed.
|
|
92
|
+
const ignore = ctx.config.circularDeps?.ignore ?? [];
|
|
93
|
+
const files = ignore.length > 0 ? allFiles.filter(f => !matchesAnyGlob(f, ignore)) : allFiles;
|
|
94
|
+
if (files.length === 0)
|
|
95
|
+
return skip('all files ignored');
|
|
96
|
+
const dpdmBin = resolveDpdm(ctx);
|
|
97
|
+
if (!dpdmBin) {
|
|
98
|
+
logger.warn('[Circular Deps]: ⚠️ dpdm could not be resolved — skipping');
|
|
99
|
+
return skip('dpdm unavailable');
|
|
100
|
+
}
|
|
101
|
+
const timeout = ctx.config.circularDeps?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
102
|
+
const reportPath = path.join(ctx.cwd, '.dpdm.temp.json');
|
|
103
|
+
try {
|
|
104
|
+
// Read the cycles from dpdm's JSON report rather than its console output.
|
|
105
|
+
// The console shapes this check used to grep for ("Circular Dependencies:",
|
|
106
|
+
// "[1] a -> b") are not what dpdm 4 prints ("• Circular Dependencies",
|
|
107
|
+
// "1) a -> b"), so no cycle ever matched and the check passed every repo,
|
|
108
|
+
// including one built solely out of a cycle. `--exit-code circular:1` gives
|
|
109
|
+
// the same answer independently: dpdm otherwise exits 0 even when it finds
|
|
110
|
+
// cycles, so the exit code alone cannot tell a clean repo from a broken run.
|
|
111
|
+
const result = await execa(process.execPath, [
|
|
112
|
+
dpdmBin,
|
|
113
|
+
'--no-warning',
|
|
114
|
+
'--no-tree',
|
|
115
|
+
'--no-circular',
|
|
116
|
+
'--exit-code',
|
|
117
|
+
'circular:1',
|
|
118
|
+
'-o',
|
|
119
|
+
reportPath,
|
|
120
|
+
...toFileArgs(files, getSourcePaths(ctx.cwd)),
|
|
121
|
+
], { cwd: ctx.cwd, reject: false, timeout });
|
|
122
|
+
if (result.timedOut) {
|
|
123
|
+
logger.warn(`[Circular Deps]: ⚠️ dpdm timed out after ${timeout}ms — skipping`);
|
|
124
|
+
return skip('dpdm timed out');
|
|
125
|
+
}
|
|
126
|
+
const report = readReport(reportPath);
|
|
127
|
+
// No report means dpdm never got far enough to write one (a tsconfig it
|
|
128
|
+
// cannot read, a file it cannot parse, a killed process). Skip: reporting
|
|
129
|
+
// that as "no cycles" hides a check that did not run, and failing on it
|
|
130
|
+
// blocks a PR that may have no cycle at all.
|
|
131
|
+
if (!report) {
|
|
132
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
133
|
+
logger.warn(`[Circular Deps]: ⚠️ dpdm wrote no report (exit ${result.exitCode ?? 'killed'})${output ? `:\n${output}` : ''}`);
|
|
134
|
+
return skip(`dpdm failed to run (exit ${result.exitCode ?? 'killed'})`);
|
|
135
|
+
}
|
|
136
|
+
const cycles = (report.circulars ?? []).filter(cycle => ignore.length === 0 || !cycle.some(file => matchesAnyGlob(file, ignore)));
|
|
137
|
+
if (cycles.length === 0)
|
|
138
|
+
return pass();
|
|
139
|
+
// Advisory by default. This check reported nothing for its whole existence
|
|
140
|
+
// (see the note above), so switching it on turns every long-standing cycle
|
|
141
|
+
// in every client repo into a finding at once — barrel files alone account
|
|
142
|
+
// for most of them. Warning first lets a repo see its cycles before they
|
|
143
|
+
// gate anything; promote with `circularDeps.severity: "error"`.
|
|
144
|
+
const sev = severityEmitter(ctx.config.circularDeps?.severity, 'warn');
|
|
145
|
+
sev.log(`[Circular Deps]: ${sev.icon} ${cycles.length} circular dependency(ies):`);
|
|
146
|
+
cycles.slice(0, 10).forEach((cycle, i) => sev.log(` ${i + 1}) ${cycle.join(' -> ')}`));
|
|
147
|
+
if (cycles.length > 10)
|
|
148
|
+
sev.log(` ... and ${cycles.length - 10} more`);
|
|
149
|
+
return sev.emit(`${cycles.length} circular dependency(ies) found`);
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
cleanupTempFile(reportPath);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Check } from '../../types.js';
|
|
2
|
+
/** One entry of knip's JSON reporter — a file plus the issues found for it. */
|
|
3
|
+
interface KnipIssue {
|
|
4
|
+
file: string;
|
|
5
|
+
dependencies?: {
|
|
6
|
+
name: string;
|
|
7
|
+
}[];
|
|
8
|
+
unlisted?: {
|
|
9
|
+
name: string;
|
|
10
|
+
}[];
|
|
11
|
+
}
|
|
12
|
+
interface KnipReport {
|
|
13
|
+
issues?: KnipIssue[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compile one `dependency.ignore` entry into a pattern knip matches the way this
|
|
17
|
+
* config documents it — a glob over the whole dependency name.
|
|
18
|
+
*
|
|
19
|
+
* knip turns any value containing `* + \ ( | { ^ $` into an *unanchored* `RegExp`
|
|
20
|
+
* and compares everything else by string equality. A glob passed straight through
|
|
21
|
+
* would therefore silence far more than it names: `eslint-*` reads as "eslint
|
|
22
|
+
* followed by zero or more dashes", tested as a substring, so it also swallows
|
|
23
|
+
* `eslint` itself. Escaping first and anchoring the result keeps `eslint-*` matching
|
|
24
|
+
* only `eslint-…`, and keeps a plain name matching exactly that name.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toKnipIgnorePattern(entry: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Pull knip's report out of a stdout stream that may carry unrelated output.
|
|
29
|
+
*
|
|
30
|
+
* knip imports the project's own config files while analyzing (a `wdio.conf.ts`
|
|
31
|
+
* for its WebdriverIO plugin, say), so any top-level side effect in them —
|
|
32
|
+
* a dotenv banner, a `console.log` in a config module — is printed before the
|
|
33
|
+
* report. Slicing from the first `{` to the last `}` therefore captured that
|
|
34
|
+
* noise instead: a dotenvx tip ending in `{ path: '…/.env' }` made the whole
|
|
35
|
+
* check fail on a repo whose report was perfectly valid.
|
|
36
|
+
*
|
|
37
|
+
* The json reporter writes the report as a single line (`JSON.stringify` with no
|
|
38
|
+
* indentation), so the last line that parses AND carries `issues` is the report.
|
|
39
|
+
* Anything else on stdout is noise, whatever braces it happens to contain.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseKnipReport(stdout: string): KnipReport | null;
|
|
42
|
+
export declare const dependencyCheck: Check;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { hasLocalConfig } from '../../../config/index.js';
|
|
4
|
+
import { execa } from '../../../utils/execa.js';
|
|
5
|
+
import { logger, cleanupTempFile, resolvePackageBin } from '../../../utils/index.js';
|
|
6
|
+
import { isNpmProject, pass, fail, warn, skip } from '../../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Config files knip discovers on its own. When a client ships one, it is the source
|
|
9
|
+
* of truth and pr-checkmate passes no config of its own — the same rule spellcheck
|
|
10
|
+
* follows for cspell.json.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors knip's own `KNIP_CONFIG_LOCATIONS` exactly. Listing a name knip does not
|
|
13
|
+
* read (`knip.config.mjs`, say) would be worse than useless: the repo would count as
|
|
14
|
+
* "configures knip itself", so `dependency.ignore` would be dropped, while knip
|
|
15
|
+
* ignored that file too — leaving the client with warnings they had silenced.
|
|
16
|
+
*/
|
|
17
|
+
const KNIP_CONFIG_FILES = [
|
|
18
|
+
'knip.json',
|
|
19
|
+
'knip.jsonc',
|
|
20
|
+
'.knip.json',
|
|
21
|
+
'.knip.jsonc',
|
|
22
|
+
'knip.ts',
|
|
23
|
+
'knip.js',
|
|
24
|
+
'knip.config.ts',
|
|
25
|
+
'knip.config.js',
|
|
26
|
+
];
|
|
27
|
+
/** Regex metacharacters to neutralise before a glob is compiled. */
|
|
28
|
+
const REGEX_SPECIALS = /[.*+?^${}()|[\]\\]/g;
|
|
29
|
+
/**
|
|
30
|
+
* Compile one `dependency.ignore` entry into a pattern knip matches the way this
|
|
31
|
+
* config documents it — a glob over the whole dependency name.
|
|
32
|
+
*
|
|
33
|
+
* knip turns any value containing `* + \ ( | { ^ $` into an *unanchored* `RegExp`
|
|
34
|
+
* and compares everything else by string equality. A glob passed straight through
|
|
35
|
+
* would therefore silence far more than it names: `eslint-*` reads as "eslint
|
|
36
|
+
* followed by zero or more dashes", tested as a substring, so it also swallows
|
|
37
|
+
* `eslint` itself. Escaping first and anchoring the result keeps `eslint-*` matching
|
|
38
|
+
* only `eslint-…`, and keeps a plain name matching exactly that name.
|
|
39
|
+
*/
|
|
40
|
+
export function toKnipIgnorePattern(entry) {
|
|
41
|
+
const escaped = entry.replace(REGEX_SPECIALS, '\\$&');
|
|
42
|
+
return `^${escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.')}$`;
|
|
43
|
+
}
|
|
44
|
+
/** knip also reads a `knip` key from package.json. */
|
|
45
|
+
function hasPackageJsonConfig(cwd) {
|
|
46
|
+
try {
|
|
47
|
+
return 'knip' in JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Pull knip's report out of a stdout stream that may carry unrelated output.
|
|
55
|
+
*
|
|
56
|
+
* knip imports the project's own config files while analyzing (a `wdio.conf.ts`
|
|
57
|
+
* for its WebdriverIO plugin, say), so any top-level side effect in them —
|
|
58
|
+
* a dotenv banner, a `console.log` in a config module — is printed before the
|
|
59
|
+
* report. Slicing from the first `{` to the last `}` therefore captured that
|
|
60
|
+
* noise instead: a dotenvx tip ending in `{ path: '…/.env' }` made the whole
|
|
61
|
+
* check fail on a repo whose report was perfectly valid.
|
|
62
|
+
*
|
|
63
|
+
* The json reporter writes the report as a single line (`JSON.stringify` with no
|
|
64
|
+
* indentation), so the last line that parses AND carries `issues` is the report.
|
|
65
|
+
* Anything else on stdout is noise, whatever braces it happens to contain.
|
|
66
|
+
*/
|
|
67
|
+
export function parseKnipReport(stdout) {
|
|
68
|
+
const lines = stdout.split('\n');
|
|
69
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
70
|
+
const line = lines[i].trim();
|
|
71
|
+
if (!line.startsWith('{'))
|
|
72
|
+
continue;
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(line);
|
|
75
|
+
if (Array.isArray(parsed.issues))
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// A noise line that merely looks like JSON — keep scanning upwards.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/** Collect the names reported for one issue type across every file in the report. */
|
|
85
|
+
function collectNames(issues, key) {
|
|
86
|
+
return [...new Set(issues.flatMap(issue => (issue[key] ?? []).map(entry => entry.name)))];
|
|
87
|
+
}
|
|
88
|
+
/** Files a given unlisted dependency is imported from, for the failure message. */
|
|
89
|
+
function filesUsing(issues, name) {
|
|
90
|
+
return issues
|
|
91
|
+
.filter(issue => (issue.unlisted ?? []).some(entry => entry.name === name))
|
|
92
|
+
.map(issue => issue.file);
|
|
93
|
+
}
|
|
94
|
+
export const dependencyCheck = {
|
|
95
|
+
name: 'Dependencies',
|
|
96
|
+
phase: 'blocking',
|
|
97
|
+
weight: 'heavy',
|
|
98
|
+
applies(ctx) {
|
|
99
|
+
if (ctx.config.dependency?.enabled === false)
|
|
100
|
+
return false;
|
|
101
|
+
return isNpmProject(ctx);
|
|
102
|
+
},
|
|
103
|
+
async run(ctx) {
|
|
104
|
+
// Resolve the bundled knip binary and run it via the current node — never
|
|
105
|
+
// shell out to `npx`, which fails (exit 127) when knip isn't on the path.
|
|
106
|
+
let knipBin;
|
|
107
|
+
try {
|
|
108
|
+
knipBin = resolvePackageBin('knip', 'bin/knip.js');
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return skip('knip unavailable');
|
|
112
|
+
}
|
|
113
|
+
// Dependency names to skip, configured in pr-checkmate.json. knip takes these
|
|
114
|
+
// from a config file only, so they go into a temp one — but a client's own knip
|
|
115
|
+
// config wins outright, exactly as it does for cspell.
|
|
116
|
+
const ignore = ctx.config.dependency?.ignore ?? [];
|
|
117
|
+
const clientOwnsConfig = hasLocalConfig(KNIP_CONFIG_FILES, ctx.cwd) || hasPackageJsonConfig(ctx.cwd);
|
|
118
|
+
let tempConfigPath = null;
|
|
119
|
+
if (ignore.length > 0 && clientOwnsConfig) {
|
|
120
|
+
// Say so out loud: a silently ignored `ignore` list looks like a broken check.
|
|
121
|
+
logger.info(`[Dependencies]: ℹ️ this repo has its own knip config, so dependency.ignore (${ignore.join(', ')}) is not applied — add those names to knip's ignoreDependencies instead.`);
|
|
122
|
+
}
|
|
123
|
+
if (ignore.length > 0 && !clientOwnsConfig) {
|
|
124
|
+
tempConfigPath = path.join(ctx.cwd, '.knip.temp.json');
|
|
125
|
+
fs.writeFileSync(tempConfigPath, JSON.stringify({ ignoreDependencies: ignore.map(toKnipIgnorePattern) }, null, 2));
|
|
126
|
+
}
|
|
127
|
+
// Only unused prod dependencies and imports missing from package.json are
|
|
128
|
+
// reported. knip's other issue types (unused devDependencies, binaries,
|
|
129
|
+
// unresolved paths) are noise here: CLI tools and shell commands legitimately
|
|
130
|
+
// have no import to find.
|
|
131
|
+
const args = [
|
|
132
|
+
knipBin,
|
|
133
|
+
'--reporter',
|
|
134
|
+
'json',
|
|
135
|
+
'--include',
|
|
136
|
+
'dependencies,unlisted',
|
|
137
|
+
'--no-config-hints',
|
|
138
|
+
'--no-exit-code',
|
|
139
|
+
'--no-progress',
|
|
140
|
+
];
|
|
141
|
+
if (tempConfigPath)
|
|
142
|
+
args.push('--config', tempConfigPath);
|
|
143
|
+
try {
|
|
144
|
+
// reject:false — knip can still exit non-zero (e.g. config hints) while
|
|
145
|
+
// writing a perfectly good report to stdout.
|
|
146
|
+
const { stdout } = await execa(process.execPath, args, {
|
|
147
|
+
cwd: ctx.cwd,
|
|
148
|
+
reject: false,
|
|
149
|
+
});
|
|
150
|
+
const result = parseKnipReport(stdout);
|
|
151
|
+
// No report on stdout → the tool couldn't produce a result; skip rather than
|
|
152
|
+
// fail the client's PR. Never fail here: a check that blocks a PR because a
|
|
153
|
+
// tool misbehaved reports our problem as the client's.
|
|
154
|
+
if (!result)
|
|
155
|
+
return skip('knip produced no report');
|
|
156
|
+
const issues = result.issues ?? [];
|
|
157
|
+
const unused = collectNames(issues, 'dependencies');
|
|
158
|
+
const missing = collectNames(issues, 'unlisted');
|
|
159
|
+
if (missing.length > 0) {
|
|
160
|
+
missing.forEach(dep => logger.error(`[Dependencies]: ❌ missing ${dep} (used in: ${filesUsing(issues, dep).join(', ')})`));
|
|
161
|
+
return fail(`missing: ${missing.join(', ')}`);
|
|
162
|
+
}
|
|
163
|
+
if (unused.length > 0) {
|
|
164
|
+
unused.forEach(dep => logger.warn(`[Dependencies]: ⚠️ unused ${dep}`));
|
|
165
|
+
return warn(`${unused.length} unused dependency(ies)`);
|
|
166
|
+
}
|
|
167
|
+
return pass();
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
if (tempConfigPath)
|
|
171
|
+
cleanupTempFile(tempConfigPath);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Check } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Dependency vulnerability scan via Anchore's `grype`. Non-blocking and ON by
|
|
4
|
+
* default — runs unless `grypeScan.enabled` is explicitly `false`. grype is a
|
|
5
|
+
* runner-dependency: it must be on the runner, and the check skips gracefully
|
|
6
|
+
* (not fails) when it is absent.
|
|
7
|
+
*
|
|
8
|
+
* grype resolves versions from lockfiles, so its raw output includes the entire
|
|
9
|
+
* transitive tree — and, because pr-checkmate installs into the consumer's
|
|
10
|
+
* node_modules, its own bundled dependencies too. Neither is something the
|
|
11
|
+
* consumer can fix. We therefore report ONLY vulnerabilities in DIRECT
|
|
12
|
+
* dependencies (those declared in the project's manifests, see
|
|
13
|
+
* `collectDirectDeps`): the set the maintainer actually controls. Findings whose
|
|
14
|
+
* ecosystem has no manifest we can parse are suppressed too, and the count of
|
|
15
|
+
* everything dropped is logged so nothing is hidden silently.
|
|
16
|
+
*
|
|
17
|
+
* The scan is confined to the project's own files: `ignoreDirs` is passed to grype
|
|
18
|
+
* as `--exclude`, and a timeout backstops the run. Both matter — cataloguing an
|
|
19
|
+
* installed `node_modules` is minutes of work whose every finding this check then
|
|
20
|
+
* throws away as third-party.
|
|
21
|
+
*/
|
|
22
|
+
export declare const grypeScanCheck: Check;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { execa } from '../../../utils/execa.js';
|
|
2
|
+
import { collectDirectDeps, logger, normalizeName } from '../../../utils/index.js';
|
|
3
|
+
import { pass, skip, warn } from '../../types.js';
|
|
4
|
+
import { notInstalled } from '../tool-install.js';
|
|
5
|
+
const SEVERITY_ORDER = ['negligible', 'low', 'medium', 'high', 'critical'];
|
|
6
|
+
/** grype artifact `type` → the ecosystem we can resolve direct deps for. */
|
|
7
|
+
const TYPE_TO_ECOSYSTEM = {
|
|
8
|
+
npm: 'npm',
|
|
9
|
+
python: 'python',
|
|
10
|
+
'go-module': 'go',
|
|
11
|
+
'rust-crate': 'rust',
|
|
12
|
+
gem: 'ruby',
|
|
13
|
+
'php-composer': 'php',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Cap for the scan. grype has no internal limit, so without this a slow scan just
|
|
17
|
+
* looks like a hung commit — an advisory check must never be able to do that.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_TIMEOUT_MS = 120000;
|
|
20
|
+
function severityRank(severity) {
|
|
21
|
+
return SEVERITY_ORDER.indexOf((severity ?? '').toLowerCase());
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Dependency vulnerability scan via Anchore's `grype`. Non-blocking and ON by
|
|
25
|
+
* default — runs unless `grypeScan.enabled` is explicitly `false`. grype is a
|
|
26
|
+
* runner-dependency: it must be on the runner, and the check skips gracefully
|
|
27
|
+
* (not fails) when it is absent.
|
|
28
|
+
*
|
|
29
|
+
* grype resolves versions from lockfiles, so its raw output includes the entire
|
|
30
|
+
* transitive tree — and, because pr-checkmate installs into the consumer's
|
|
31
|
+
* node_modules, its own bundled dependencies too. Neither is something the
|
|
32
|
+
* consumer can fix. We therefore report ONLY vulnerabilities in DIRECT
|
|
33
|
+
* dependencies (those declared in the project's manifests, see
|
|
34
|
+
* `collectDirectDeps`): the set the maintainer actually controls. Findings whose
|
|
35
|
+
* ecosystem has no manifest we can parse are suppressed too, and the count of
|
|
36
|
+
* everything dropped is logged so nothing is hidden silently.
|
|
37
|
+
*
|
|
38
|
+
* The scan is confined to the project's own files: `ignoreDirs` is passed to grype
|
|
39
|
+
* as `--exclude`, and a timeout backstops the run. Both matter — cataloguing an
|
|
40
|
+
* installed `node_modules` is minutes of work whose every finding this check then
|
|
41
|
+
* throws away as third-party.
|
|
42
|
+
*/
|
|
43
|
+
export const grypeScanCheck = {
|
|
44
|
+
name: 'Grype Scan',
|
|
45
|
+
phase: 'informational',
|
|
46
|
+
weight: 'heavy',
|
|
47
|
+
applies(ctx) {
|
|
48
|
+
return ctx.config.grypeScan?.enabled !== false;
|
|
49
|
+
},
|
|
50
|
+
async run(ctx) {
|
|
51
|
+
const minSeverity = (ctx.config.grypeScan?.minSeverity ?? 'medium').toLowerCase();
|
|
52
|
+
const minRank = severityRank(minSeverity);
|
|
53
|
+
const ignore = (ctx.config.grypeScan?.ignore ?? []).map(s => s.toLowerCase());
|
|
54
|
+
// Never walk `ignoreDirs` — the same contract every other check honours, and
|
|
55
|
+
// here it is the difference between a usable check and an unusable one. A bare
|
|
56
|
+
// `grype dir:.` catalogs every file under `node_modules`, which on this repo took
|
|
57
|
+
// 355s versus 0.8s with the directory excluded, for byte-for-byte the same
|
|
58
|
+
// findings: grype resolves package versions from the lockfile, and installed
|
|
59
|
+
// third-party code is not something the client could fix anyway (see below).
|
|
60
|
+
const excludes = (ctx.config.ignoreDirs ?? []).flatMap(dir => ['--exclude', `**/${dir}/**`]);
|
|
61
|
+
const timeout = ctx.config.grypeScan?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
62
|
+
const result = await execa('grype', ['dir:.', '-o', 'json', '-q', ...excludes], {
|
|
63
|
+
cwd: ctx.cwd,
|
|
64
|
+
reject: false,
|
|
65
|
+
timeout,
|
|
66
|
+
});
|
|
67
|
+
// execa v9 with reject:false resolves (not throws) with `code: 'ENOENT'`
|
|
68
|
+
// when the binary is absent — detect the missing scanner on the result.
|
|
69
|
+
if (result.code === 'ENOENT')
|
|
70
|
+
return notInstalled('grype');
|
|
71
|
+
if (result.timedOut) {
|
|
72
|
+
logger.warn(`[Grype Scan]: ⚠️ grype timed out after ${timeout}ms — skipping`);
|
|
73
|
+
return skip('grype timed out');
|
|
74
|
+
}
|
|
75
|
+
let report;
|
|
76
|
+
try {
|
|
77
|
+
report = JSON.parse(result.stdout);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return warn('grype produced no parseable output');
|
|
81
|
+
}
|
|
82
|
+
const matches = report.matches ?? [];
|
|
83
|
+
const direct = collectDirectDeps(ctx.cwd);
|
|
84
|
+
// Keep only: severity >= threshold, a direct dependency, and not ignored.
|
|
85
|
+
const isDirect = (m) => {
|
|
86
|
+
const ecosystem = TYPE_TO_ECOSYSTEM[m.artifact?.type ?? ''];
|
|
87
|
+
const names = ecosystem && direct.get(ecosystem);
|
|
88
|
+
if (!names)
|
|
89
|
+
return false; // unknown/unparseable ecosystem → not our concern
|
|
90
|
+
return names.has(normalizeName(ecosystem, m.artifact?.name ?? ''));
|
|
91
|
+
};
|
|
92
|
+
const isIgnored = (m) => ignore.includes((m.vulnerability?.id ?? '').toLowerCase()) ||
|
|
93
|
+
ignore.includes((m.artifact?.name ?? '').toLowerCase());
|
|
94
|
+
const atSeverity = matches.filter(m => severityRank(m.vulnerability?.severity) >= minRank);
|
|
95
|
+
const findings = atSeverity.filter(m => isDirect(m) && !isIgnored(m));
|
|
96
|
+
const suppressed = atSeverity.length - findings.length;
|
|
97
|
+
if (suppressed > 0) {
|
|
98
|
+
logger.info(`[Grype Scan]: suppressed ${suppressed} finding(s) — transitive/third-party (not directly fixable) or ignored`);
|
|
99
|
+
}
|
|
100
|
+
if (findings.length === 0) {
|
|
101
|
+
logger.info('[Grype Scan]: ✅ No known vulnerabilities in direct dependencies');
|
|
102
|
+
return pass();
|
|
103
|
+
}
|
|
104
|
+
logger.warn(`[Grype Scan]: ⚠️ vulnerabilities in direct dependencies (>= ${minSeverity}):`);
|
|
105
|
+
findings
|
|
106
|
+
.slice(0, 10)
|
|
107
|
+
.forEach(m => logger.warn(` ${m.vulnerability?.id ?? 'unknown'} — ${m.artifact?.name ?? '?'}@${m.artifact?.version ?? '?'} (${m.vulnerability?.severity ?? '?'})`));
|
|
108
|
+
if (findings.length > 10)
|
|
109
|
+
logger.warn(` ... and ${findings.length - 10} more`);
|
|
110
|
+
return warn(`${findings.length} direct-dependency vulnerability(ies) at "${minSeverity}"+ (grype)`);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { execa } from '../../../utils/execa.js';
|
|
2
|
+
import { logger, resolvePackageBin, getOwnDependencyClosure } from '../../../utils/index.js';
|
|
3
|
+
import { isNpmProject, pass, fail, skip } from '../../types.js';
|
|
4
|
+
const FORBIDDEN_LICENSES = ['GPL-2.0', 'GPL-3.0', 'AGPL-3.0', 'LGPL-2.0', 'LGPL-2.1', 'LGPL-3.0'];
|
|
5
|
+
/** Strip the trailing "@version" to get the bare package name (scoped-safe). */
|
|
6
|
+
function bareName(pkgKey) {
|
|
7
|
+
const at = pkgKey.lastIndexOf('@');
|
|
8
|
+
return at > 0 ? pkgKey.slice(0, at) : pkgKey;
|
|
9
|
+
}
|
|
10
|
+
export const licenseCheck = {
|
|
11
|
+
name: 'License Check',
|
|
12
|
+
phase: 'blocking',
|
|
13
|
+
applies(ctx) {
|
|
14
|
+
return isNpmProject(ctx);
|
|
15
|
+
},
|
|
16
|
+
async run(ctx) {
|
|
17
|
+
const allowPackages = new Set(ctx.config.licenseCheck?.allowPackages ?? []);
|
|
18
|
+
const allowLicenses = ctx.config.licenseCheck?.allowLicenses ?? [];
|
|
19
|
+
// pr-checkmate installs into the consumer's node_modules, so its own runtime
|
|
20
|
+
// deps (eslint plugins, cspell dictionaries, …) leak into the client's
|
|
21
|
+
// production tree. Those are our dependencies, not the client's — never fail
|
|
22
|
+
// a client's PR because of a license we ourselves pulled in.
|
|
23
|
+
const ownDeps = getOwnDependencyClosure();
|
|
24
|
+
// Resolve the bundled license-checker binary and run it via the current node — never
|
|
25
|
+
// shell out to `npx`, which fails (exit 127) when license-checker isn't on the path.
|
|
26
|
+
let licenseCheckerBin;
|
|
27
|
+
try {
|
|
28
|
+
licenseCheckerBin = resolvePackageBin('license-checker-rseidelsohn', 'bin/license-checker-rseidelsohn.js');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return skip('license-checker unavailable');
|
|
32
|
+
}
|
|
33
|
+
const { stdout, failed } = await execa(process.execPath, [licenseCheckerBin, '--json', '--production'], { cwd: ctx.cwd, reject: false });
|
|
34
|
+
let licenses;
|
|
35
|
+
try {
|
|
36
|
+
licenses = JSON.parse(stdout);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Tool couldn't produce parseable output; skip rather than fail the client's PR.
|
|
40
|
+
return skip(failed ? 'license-checker failed to run' : 'license-checker produced invalid JSON');
|
|
41
|
+
}
|
|
42
|
+
const violations = [];
|
|
43
|
+
for (const [pkg, info] of Object.entries(licenses)) {
|
|
44
|
+
const name = bareName(pkg);
|
|
45
|
+
if (allowPackages.has(name) || ownDeps.has(name))
|
|
46
|
+
continue;
|
|
47
|
+
const raw = info.licenses ?? '';
|
|
48
|
+
const license = Array.isArray(raw) ? raw.join(' OR ') : raw;
|
|
49
|
+
if (allowLicenses.some(a => license.includes(a)))
|
|
50
|
+
continue;
|
|
51
|
+
if (FORBIDDEN_LICENSES.some(f => license.includes(f))) {
|
|
52
|
+
violations.push(`${pkg}: ${license}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (violations.length > 0) {
|
|
56
|
+
violations.forEach(v => logger.error(`[License Check]: ❌ ${v}`));
|
|
57
|
+
return fail(`${violations.length} forbidden license(s)`);
|
|
58
|
+
}
|
|
59
|
+
return pass();
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'node:url';
|
|
2
|
+
import { dirname as __dirnameFn } from 'node:path';
|
|
3
|
+
const __dirname = __dirnameFn(__fileURLToPath(import.meta.url));
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { execa } from '../../../utils/execa.js';
|
|
7
|
+
import { logger, findOwnPackageRoot } from '../../../utils/index.js';
|
|
8
|
+
import { isNpmProject, pass, fail, skip } from '../../types.js';
|
|
9
|
+
const SEVERITY_ORDER = ['info', 'low', 'moderate', 'high', 'critical'];
|
|
10
|
+
function isSeverityAtLeast(severity, level) {
|
|
11
|
+
const sevIdx = SEVERITY_ORDER.indexOf(severity);
|
|
12
|
+
const lvlIdx = SEVERITY_ORDER.indexOf(level);
|
|
13
|
+
return sevIdx >= 0 && lvlIdx >= 0 && sevIdx >= lvlIdx;
|
|
14
|
+
}
|
|
15
|
+
function getOwnPackageName() {
|
|
16
|
+
try {
|
|
17
|
+
const ownPkg = path.join(findOwnPackageRoot() ?? path.resolve(__dirname, '../../../..'), 'package.json');
|
|
18
|
+
return JSON.parse(fs.readFileSync(ownPkg, 'utf-8')).name ?? null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* npm audit — only direct-dependency vulnerabilities block. Transitive ones and
|
|
26
|
+
* vulnerabilities in pr-checkmate itself are ignored (the client cannot fix those).
|
|
27
|
+
*/
|
|
28
|
+
export const npmAuditCheck = {
|
|
29
|
+
name: 'NPM Audit',
|
|
30
|
+
phase: 'blocking',
|
|
31
|
+
applies(ctx) {
|
|
32
|
+
if (ctx.config.security?.['npm-audit']?.enabled === false)
|
|
33
|
+
return false;
|
|
34
|
+
return isNpmProject(ctx);
|
|
35
|
+
},
|
|
36
|
+
async run(ctx) {
|
|
37
|
+
// `high`, not `moderate`. A moderate CVE in a direct dependency often has no
|
|
38
|
+
// fix available, and blocking on it stops the team shipping anything at all —
|
|
39
|
+
// indefinitely, for a risk they have usually already accepted. A gate that
|
|
40
|
+
// cannot be satisfied gets switched off, and then the high ones stop blocking
|
|
41
|
+
// too. Clients who want the stricter line set `security.npm-audit.auditLevel`.
|
|
42
|
+
const auditLevel = ctx.config.security?.['npm-audit']?.auditLevel ?? 'high';
|
|
43
|
+
const ignore = new Set((ctx.config.security?.['npm-audit']?.ignore ?? []).map(name => name.toLowerCase()));
|
|
44
|
+
const ownName = getOwnPackageName();
|
|
45
|
+
const { stdout } = await execa('npm', ['audit', '--json'], { cwd: ctx.cwd, reject: false });
|
|
46
|
+
// `npm audit` reports registry/lockfile problems as JSON on stdout, but not
|
|
47
|
+
// always: an absent npm (execa resolves with `code: 'ENOENT'`), a killed
|
|
48
|
+
// process or a proxy that answers with HTML all leave stdout unparseable.
|
|
49
|
+
// Parsing it bare made those cases throw, and a throw in a blocking check is
|
|
50
|
+
// a failed PR — the client saw "NPM Audit threw: Unexpected end of JSON
|
|
51
|
+
// input" and a red gate for an environment problem they did not cause.
|
|
52
|
+
let report;
|
|
53
|
+
try {
|
|
54
|
+
report = JSON.parse(stdout);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
logger.warn('[NPM Audit]: ⚠️ npm audit produced no parseable report — skipping');
|
|
58
|
+
return skip('npm audit produced no parseable report');
|
|
59
|
+
}
|
|
60
|
+
// A repo with no lockfile, or a registry npm cannot reach, yields
|
|
61
|
+
// `{"error":{"code":"ENOLOCK",…}}` — no `vulnerabilities` key at all. Passing
|
|
62
|
+
// on that reads as "no vulnerabilities" when nothing was actually audited.
|
|
63
|
+
if (report.error) {
|
|
64
|
+
const detail = report.error.summary ?? report.error.code ?? 'unknown error';
|
|
65
|
+
logger.warn(`[NPM Audit]: ⚠️ npm could not audit this project — ${detail}`);
|
|
66
|
+
return skip(`npm audit unavailable: ${report.error.code ?? 'error'}`);
|
|
67
|
+
}
|
|
68
|
+
const relevant = Object.values(report.vulnerabilities ?? {}).filter(v => v.isDirect && v.name !== ownName && isSeverityAtLeast(v.severity, auditLevel));
|
|
69
|
+
const [ignored, directVulns] = relevant.reduce(([skip, block], v) => {
|
|
70
|
+
(ignore.has(v.name.toLowerCase()) ? skip : block).push(v);
|
|
71
|
+
return [skip, block];
|
|
72
|
+
}, [[], []]);
|
|
73
|
+
// Ignored advisories stay visible as warnings — an exception is not a silent drop.
|
|
74
|
+
ignored.forEach(v => logger.warn(`[NPM Audit]: ⚠ ${v.name} — severity: ${v.severity} (ignored via config)`));
|
|
75
|
+
if (directVulns.length === 0)
|
|
76
|
+
return pass();
|
|
77
|
+
directVulns.forEach(v => logger.error(`[NPM Audit]: ❌ ${v.name} — severity: ${v.severity}`));
|
|
78
|
+
return fail(`${directVulns.length} direct vulnerability(ies) at "${auditLevel}"+`);
|
|
79
|
+
},
|
|
80
|
+
};
|