@sasjs/lint 4.3.2 → 4.3.3

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.
@@ -1,92 +0,0 @@
1
- name: Mutation Watch
2
-
3
- # The per-PR mutation gate compares a branch's changed files with a baseline, so
4
- # the baseline is the thing that has to stay honest: if it is never refreshed,
5
- # files that improve keep their old count and the gate stops asking for more.
6
- #
7
- # This runs the whole source tree weekly, prints the new baseline, and fails when
8
- # any file carries MORE surviving mutants than the recorded one - which means a
9
- # PR was merged without the gate seeing it (a push to main, a config change, a
10
- # file the gate skipped). Failure files an issue, as audit-watch.yml does, and
11
- # only a genuine regression does so.
12
-
13
- on:
14
- schedule:
15
- - cron: '0 7 * * 1' # Mondays, 07:00 UTC
16
- workflow_dispatch:
17
-
18
- jobs:
19
- watch:
20
- runs-on: ubuntu-latest
21
- # the whole tree takes ~20-30 minutes; bounded so a hang cannot hold a runner
22
- # for the six-hour default
23
- timeout-minutes: 90
24
- permissions:
25
- contents: read
26
- issues: write
27
-
28
- steps:
29
- - uses: actions/checkout@v4
30
-
31
- - name: Use Node.js
32
- uses: actions/setup-node@v4
33
- with:
34
- node-version: 22
35
- cache: npm
36
-
37
- - name: Install Dependencies
38
- run: npm ci
39
-
40
- - name: Mutate the whole source tree
41
- id: watch
42
- env:
43
- MUTATION_WATCH_REPORT: ${{ runner.temp }}/mutation-watch-report.md
44
- run: node scripts/mutation-gate.mjs weekly
45
-
46
- - name: Keep the report
47
- if: always()
48
- uses: actions/upload-artifact@v4
49
- with:
50
- name: mutation-watch-report
51
- path: |
52
- reports/mutation/mutation.json
53
- ${{ runner.temp }}/mutation-watch-report.md
54
- if-no-files-found: ignore
55
-
56
- report:
57
- needs: watch
58
- # a failure files an issue; a cancelled run does so only when the schedule
59
- # cancelled it (the 90-minute timeout), so cancelling a manual run by hand
60
- # does not claim a regression that never happened
61
- if: >-
62
- always() &&
63
- (needs.watch.result == 'failure' ||
64
- (needs.watch.result == 'cancelled' && github.event_name == 'schedule'))
65
- runs-on: ubuntu-latest
66
- timeout-minutes: 5
67
- permissions:
68
- contents: read
69
- issues: write
70
-
71
- steps:
72
- - name: File an issue
73
- env:
74
- GH_TOKEN: ${{ github.token }}
75
- run: |
76
- title='Mutation watch: a file carries more surviving mutants than the baseline'
77
- existing=$(gh issue list --state open --search "\"$title\" in:title" \
78
- --json number --jq '.[0].number // empty')
79
-
80
- if [ -n "$existing" ]; then
81
- echo "already tracked in #$existing"
82
- exit 0
83
- fi
84
-
85
- gh issue create --title "$title" --body \
86
- "The weekly mutation run found a file carrying more surviving mutants than mutation-baseline.json records, so behaviour was merged without a test that fails on it.
87
-
88
- The run names the file and each surviving mutant (file:line:column, mutator, replacement), and prints the refreshed baseline:
89
-
90
- $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID
91
-
92
- Close this once a spec kills them and mutation-baseline.json is updated from that run."
@@ -1,169 +0,0 @@
1
- # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
- # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3
-
4
- name: SASjs Lint Build and Publish
5
-
6
- on:
7
- push:
8
- branches:
9
- - main
10
- workflow_dispatch:
11
-
12
- # One release at a time. Both triggers publish, and the push trigger fires on
13
- # every release commit @semantic-release/git pushes back to main, so a
14
- # dispatched recovery run can interleave with a push-triggered one. Two
15
- # semantic-release processes racing the same tag and the same npm publish is a
16
- # new way to strand a release - the state the retry below exists to recover.
17
- # `cancel-in-progress: false` queues the second run rather than cancelling the
18
- # first, because cancelling a run midway through a release is itself a way to
19
- # leave a tag pushed and the release missing.
20
- concurrency:
21
- group: publish
22
- cancel-in-progress: false
23
-
24
- jobs:
25
- build:
26
- runs-on: ubuntu-latest
27
- permissions:
28
- contents: write
29
- id-token: write # required for npm provenance (trusted publisher)
30
- issues: write # optional: lets @semantic-release/github comment on issues
31
- pull-requests: write # optional: lets @semantic-release/github comment on PRs
32
-
33
- strategy:
34
- matrix:
35
- node-version: [22]
36
-
37
- steps:
38
- # Mint a short-lived GitHub App token (bypass-capable)
39
- - name: Generate token
40
- id: app-token
41
- uses: actions/create-github-app-token@v1
42
- with:
43
- app-id: ${{ secrets.APP_ID }}
44
- private-key: ${{ secrets.APP_PRIVATE_KEY }}
45
-
46
- # Checkout using the app token so the release commit can be pushed
47
- - uses: actions/checkout@v4
48
- with:
49
- token: ${{ steps.app-token.outputs.token }}
50
- persist-credentials: true
51
- fetch-depth: 0 # semantic-release needs full history + tags
52
-
53
- - name: Use Node.js ${{ matrix.node-version }}
54
- uses: actions/setup-node@v4
55
- with:
56
- node-version: ${{ matrix.node-version }}
57
- registry-url: https://registry.npmjs.org
58
-
59
- # Node 22 ships npm 10.x — OIDC trusted publishing needs npm >= 11.5.1
60
- - name: Update npm
61
- run: npm install -g npm@latest
62
-
63
- - name: Install Dependencies
64
- run: npm ci
65
-
66
- - name: Check Code Style
67
- run: npm run lint
68
-
69
- - name: Run Unit Tests
70
- run: npm test
71
-
72
- - name: Build Project
73
- run: npm run build
74
-
75
- - name: Clean up ready for publishing
76
- run: npm run publishInit
77
-
78
- - name: Semantic Release
79
- # The release toolchain is deliberately not a project dependency: every
80
- # @semantic-release plugin declares a `semantic-release` peer, which npm
81
- # auto-installs together with the `npm` package semantic-release bundles
82
- # - and that bundled npm carries advisories nothing here can fix.
83
- # Installing it here, pinned, keeps the dependency tree clean. Keep
84
- # these versions in step with `release.plugins` in package.json.
85
- #
86
- # semantic-release pushes twice: @semantic-release/git pushes the release
87
- # commit to the branch, then core pushes the tag in a second, tags-only
88
- # push. When that second push is rejected the branch keeps the release
89
- # commit - package.json at the new version - with no tag: no GitHub
90
- # release, no npm publish, and every later run dying in the exec plugin's
91
- # `npm version` with "Version not changed", a state only a hand repair
92
- # clears. sasjs/adapter hit exactly that on 2026-10-17.
93
- #
94
- # The tag push runs after the tag exists locally and after the branch is
95
- # updated, so one more attempt has only the tag to push. Retry exactly
96
- # that case: a local tag alone does not identify it, because the tag is
97
- # also there when the failure came later (the GitHub release), and
98
- # retrying that finds the tag restored by semantic-release's own fetch,
99
- # decides there is nothing to release, exits 0 and leaves the step green
100
- # with the release still missing - so require the tag to be absent from
101
- # the remote as well.
102
- #
103
- # Only a definitive answer counts. `git ls-remote --exit-code` exits 2 for
104
- # an absent ref but 128 when the query itself fails (network, auth,
105
- # unreachable remote), and treating every non-zero exit as "absent" would
106
- # retry on the transient GitHub-side failures that are the very class of
107
- # incident this retry repairs. So 2 retries, 0 and anything else fail the
108
- # step.
109
- run: |
110
- run_release() {
111
- npx --yes \
112
- --package semantic-release@25.0.9 \
113
- --package @semantic-release/exec@7.1.0 \
114
- --package @semantic-release/git@11.0.1 \
115
- semantic-release
116
- }
117
-
118
- if run_release; then
119
- exit 0
120
- fi
121
-
122
- version=$(node -p "require('./package.json').version")
123
-
124
- if ! git rev-parse -q --verify "refs/tags/v$version" >/dev/null; then
125
- echo "No local v$version tag, so the failure came before the tag was created - not retrying." >&2
126
- exit 1
127
- fi
128
-
129
- ls_rc=0
130
- git ls-remote --exit-code --tags origin "refs/tags/v$version" >/dev/null 2>&1 || ls_rc=$?
131
-
132
- if [ "$ls_rc" -eq 0 ]; then
133
- echo "v$version is already on the remote, so the failure came after the tag push - not retrying." >&2
134
- exit 1
135
- fi
136
-
137
- if [ "$ls_rc" -ne 2 ]; then
138
- echo "Could not establish whether v$version is on the remote (git ls-remote exited $ls_rc) - not retrying." >&2
139
- exit 1
140
- fi
141
-
142
- echo "Retrying: v$version exists locally but not on the remote, so the tag push is what failed."
143
- git tag -d "v$version"
144
- run_release
145
- env:
146
- GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # <-- app token, not secrets.GITHUB_TOKEN
147
-
148
- # semantic-release only releases when it finds a releasable commit, and the
149
- # angular convention this repo is configured with does not treat `chore:` or
150
- # `docs:` as one. This step ran on `if: success()` regardless, so a merge with
151
- # no releasable commit published the version already in package.json, which
152
- # npm rejects with "You cannot publish over the previously published
153
- # versions". Compare against the registry instead, and publish only when
154
- # there is something new.
155
- - name: Publish to npm with trusted publisher
156
- id: publish
157
- if: success()
158
- run: |
159
- local_version=$(node -p "require('./package.json').version")
160
- published_version=$(npm view @sasjs/lint version 2>/dev/null || echo "")
161
-
162
- if [ "$local_version" = "$published_version" ]; then
163
- echo "released=false" >> "$GITHUB_OUTPUT"
164
- echo "Nothing to publish - $local_version is already on npm"
165
- exit 0
166
- fi
167
-
168
- npm publish --access public
169
- echo "released=true" >> "$GITHUB_OUTPUT"
package/.gitpod.yml DELETED
@@ -1,8 +0,0 @@
1
- # This configuration file was automatically generated by Gitpod.
2
- # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
3
- # and commit this file to your remote git repository to share the goodness with others.
4
-
5
- tasks:
6
- - init: npm install && npm run build
7
-
8
-
@@ -1,16 +0,0 @@
1
- # Hooks for the pre-commit framework - https://pre-commit.com
2
- #
3
- # The repo also ships native hooks in .git-hooks/, covering the secret scan, a
4
- # 2MB commit size limit and Conventional Commits. But .npmrc sets
5
- # ignore-scripts=true, so the `prepare` script that would activate them never
6
- # runs on `npm i` - a fresh clone commits unchecked until the one-time command
7
- # in .github/CONTRIBUTING.md is run by hand.
8
- #
9
- # This file is the standard route instead: install the framework once with
10
- # `pre-commit install`, and the secret scan runs on every commit. The hook is
11
- # pinned to an exact gitleaks release tag.
12
- repos:
13
- - repo: https://github.com/gitleaks/gitleaks
14
- rev: v8.30.1
15
- hooks:
16
- - id: gitleaks
package/.prettierrc DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "trailingComma": "none",
3
- "tabWidth": 2,
4
- "semi": false,
5
- "singleQuote": true
6
- }
package/.sasjslint DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "noTrailingSpaces": true,
3
- "noEncodedPasswords": true,
4
- "hasDoxygenHeader": true,
5
- "noSpacesInFileNames": true,
6
- "maxLineLength": 80,
7
- "lowerCaseFileNames": true,
8
- "noTabIndentation": true,
9
- "indentationMultiple": 2,
10
- "hasMacroNameInMend": true,
11
- "noNestedMacros": true,
12
- "hasMacroParentheses": true
13
- }
package/jest.config.js DELETED
@@ -1,62 +0,0 @@
1
- module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'node',
4
- // A Stryker run keeps a sandbox under .stryker-tmp with a copy of src/ in it.
5
- // Without this, any jest run that overlaps a mutation run collects every spec
6
- // twice (measured: 946 tests instead of 464) and reports failures from files
7
- // that are being mutated. Anchored to <rootDir> on purpose: inside the sandbox
8
- // the whole project IS under .stryker-tmp, and an unanchored pattern would make
9
- // Stryker's own dry run collect nothing ("No tests were executed").
10
- testPathIgnorePatterns: [
11
- '/node_modules/',
12
- '<rootDir>/.stryker-tmp/',
13
- '<rootDir>/build/'
14
- ],
15
- coverageThreshold: {
16
- global: {
17
- branches: 80,
18
- functions: 80,
19
- lines: 80,
20
- statements: -10
21
- },
22
- // Per-path floors, so a change that hollows out one area cannot hide behind
23
- // the aggregate. Every number is the coverage measured when this was written,
24
- // rounded down, so the block is a ratchet: raise one when the area improves,
25
- // never lower one to get a branch green. `statements` is a PERCENTAGE unless
26
- // it is negative, where `-N` reads as "at most N uncovered statements" - so a
27
- // plain `0` asserts nothing at all (0% coverage passes), which is why the two
28
- // fully-covered areas ask for 100 and the three with known gaps carry a budget
29
- // measured from the tree on the day this landed.
30
- './src/utils/': {
31
- branches: 95,
32
- functions: 100,
33
- lines: 99,
34
- statements: -4
35
- },
36
- './src/rules/': {
37
- branches: 95,
38
- functions: 100,
39
- lines: 100,
40
- statements: 100
41
- },
42
- './src/lint/': {
43
- branches: 85,
44
- functions: 100,
45
- lines: 100,
46
- statements: -2
47
- },
48
- './src/format/': {
49
- branches: 90,
50
- functions: 100,
51
- lines: 100,
52
- statements: 100
53
- },
54
- './src/types/': {
55
- branches: 95,
56
- functions: 100,
57
- lines: 96,
58
- statements: -4
59
- }
60
- },
61
- collectCoverageFrom: ['src/**/{!(index|formatExample|lintExample),}.ts']
62
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Jest config for Stryker runs.
3
- *
4
- * The only difference from jest.config.js is that coverage is off: Stryker
5
- * instruments the code itself, and the coverage thresholds would fail a
6
- * single-mutant run. The suite itself runs in full, so the weekly baseline is
7
- * built on every test - a skipped spec here would record mutants it covers as
8
- * legitimate survivors and let them into the baseline permanently.
9
- */
10
- const base = require('./jest.config.js')
11
-
12
- module.exports = {
13
- ...base,
14
- collectCoverage: false,
15
- coverageThreshold: undefined,
16
- collectCoverageFrom: undefined
17
- }
@@ -1,30 +0,0 @@
1
- {
2
- "_comment": "Surviving-mutant counts per file, used by scripts/mutation-gate.mjs. Refresh from the output of npm run mutation:watch; raise a count only with a reason in the PR that raises it.",
3
- "generatedAt": "2026-10-25T20:06:38Z",
4
- "files": {
5
- "src/rules/file/noUnusedMacros.ts": {
6
- "score": 100.0,
7
- "survived": 0,
8
- "killed": 37,
9
- "timeout": 0
10
- },
11
- "src/utils/getIndicesOf.ts": {
12
- "score": 100.0,
13
- "survived": 0,
14
- "killed": 15,
15
- "timeout": 6
16
- },
17
- "src/utils/isIgnored.ts": {
18
- "score": 100.0,
19
- "survived": 0,
20
- "killed": 25,
21
- "timeout": 0
22
- },
23
- "src/utils/parseMacroCalls.ts": {
24
- "score": 92.98,
25
- "survived": 25,
26
- "killed": 293,
27
- "timeout": 38
28
- }
29
- }
30
- }
@@ -1,249 +0,0 @@
1
- /**
2
- * Watches the dev-tree audit exemption declared in `.github/workflows/build.yml`.
3
- *
4
- * That workflow gates on `npm audit --omit=dev` because the dev tree carries one
5
- * advisory nothing in this repo can fix: GHSA-hp3w-g68c-fv3c (sprintf-js,
6
- * reached through @jest/transform -> babel-plugin-istanbul ->
7
- * @istanbuljs/load-nyc-config -> js-yaml 3 -> argparse). Every package in that
8
- * chain is at its latest published version and the advisory lists no patched
9
- * release, so gating on it would mean a permanently red build.
10
- *
11
- * An exemption with no expiry is how a scoped gate becomes a permanent one, so
12
- * this script re-checks it weekly and reports when it is out of date:
13
- *
14
- * - a dev-tree advisory outside EXEMPTIONS appears - the exemption is too narrow
15
- * - an EXEMPTIONS advisory no longer appears in the tree - the exemption is dead
16
- * - a WATCHED_FOR_RELEASE package publishes a version newer than the one
17
- * recorded here - a fix may now exist, so re-evaluate and drop the exemption
18
- * - an EXEMPTIONS id is missing from the build.yml comment - the gate and this
19
- * script disagree about what is exempt
20
- *
21
- * Any of those exits 1, failing the scheduled run and making the workflow file an
22
- * issue. The known advisory on its own does NOT exit 1: it is expected, and a job
23
- * that is red every week trains people to ignore red.
24
- *
25
- * Run with `node scripts/audit-watch.mjs`.
26
- */
27
- import { execFileSync } from 'node:child_process'
28
- import { appendFileSync, readFileSync, writeFileSync } from 'node:fs'
29
- import path from 'node:path'
30
- import { fileURLToPath } from 'node:url'
31
-
32
- const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
33
- const BUILD_WORKFLOW = path.join('.github', 'workflows', 'build.yml')
34
-
35
- /**
36
- * Advisories the dev-scoped gate in build.yml is allowed to leave unaddressed.
37
- * Each one must also be named in that workflow's comment - the check below
38
- * enforces it, so the gate and this list cannot drift apart.
39
- */
40
- const EXEMPTIONS = ['GHSA-hp3w-g68c-fv3c']
41
-
42
- /**
43
- * Packages whose published version decides whether an exemption is still
44
- * justified. `newestKnown` is the newest release at the time the exemption was
45
- * written; a newer one on the registry means a fix may exist and the exemption
46
- * deserves a fresh look rather than another year of silence.
47
- */
48
- const WATCHED_FOR_RELEASE = [{ name: 'sprintf-js', newestKnown: '1.1.3' }]
49
-
50
- /**
51
- * `npm audit --json` exits 1 whenever it reports anything, so the exit code says
52
- * nothing about whether the command worked - parse the report instead.
53
- */
54
- const runAudit = () => {
55
- let stdout
56
- try {
57
- stdout = execFileSync('npm', ['audit', '--json'], {
58
- encoding: 'utf8',
59
- stdio: ['ignore', 'pipe', 'ignore']
60
- })
61
- } catch (error) {
62
- stdout = error.stdout
63
- }
64
- if (!stdout || !stdout.trim()) {
65
- throw new Error('npm audit produced no JSON - run `npm ci` first')
66
- }
67
- return JSON.parse(stdout)
68
- }
69
-
70
- /**
71
- * The advisories the report names, keyed by GHSA id. A `via` entry is a bare
72
- * package name when the advisory is inherited from a dependency, and an object
73
- * carrying the GHSA url when it is the root of the chain - only the latter are
74
- * advisories in their own right.
75
- */
76
- const collectAdvisories = (report) => {
77
- const advisories = new Map()
78
-
79
- for (const [packageName, info] of Object.entries(report?.vulnerabilities ?? {})) {
80
- for (const via of info?.via ?? []) {
81
- if (typeof via !== 'object' || via === null) continue
82
- const id = String(via.url ?? '').split('/').pop()
83
- if (!id?.startsWith('GHSA-')) continue
84
-
85
- const entry = advisories.get(id) ?? {
86
- severity: via.severity,
87
- title: via.title,
88
- packages: []
89
- }
90
- entry.packages.push(packageName)
91
- advisories.set(id, entry)
92
- }
93
- }
94
-
95
- return advisories
96
- }
97
-
98
- /** The version the registry currently resolves `latest` to, or null if offline. */
99
- const latestPublished = (name) => {
100
- try {
101
- return execFileSync('npm', ['view', name, 'version'], {
102
- encoding: 'utf8',
103
- stdio: ['ignore', 'pipe', 'ignore']
104
- }).trim()
105
- } catch {
106
- return null
107
- }
108
- }
109
-
110
- /**
111
- * Numeric dotted comparison, so `1.1.10` sorts above `1.1.9`. A prerelease
112
- * suffix is dropped from both sides first: `1.1.3-beta.1` is not newer than
113
- * `1.1.3`, and a prerelease that happens to be tagged `latest` must not raise
114
- * the alarm by itself.
115
- */
116
- const isNewer = (candidate, baseline) => {
117
- const a = candidate.split('-')[0].split('.')
118
- const b = baseline.split('-')[0].split('.')
119
- for (let i = 0; i < Math.max(a.length, b.length); i++) {
120
- const left = a[i] ?? '0'
121
- const right = b[i] ?? '0'
122
- if (left === right) continue
123
- const leftNumber = Number(left)
124
- const rightNumber = Number(right)
125
- if (Number.isNaN(leftNumber) || Number.isNaN(rightNumber)) {
126
- return left > right
127
- }
128
- return leftNumber > rightNumber
129
- }
130
- return false
131
- }
132
-
133
- /**
134
- * Write the report to the step summary and to the file the workflow reads.
135
- * Wrapped so a failure to report cannot mask the failure being reported - and
136
- * so the workflow's `gh issue create --body-file` always has a file, including
137
- * on the path below where the watch itself broke.
138
- */
139
- const writeReport = (summary) => {
140
- try {
141
- if (process.env.GITHUB_STEP_SUMMARY) {
142
- appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary)
143
- }
144
- if (process.env.AUDIT_WATCH_REPORT) {
145
- writeFileSync(process.env.AUDIT_WATCH_REPORT, summary)
146
- }
147
- } catch (error) {
148
- console.error(`could not write the report: ${error.message}`)
149
- }
150
- }
151
-
152
- const main = () => {
153
- const report = runAudit()
154
- const advisories = collectAdvisories(report)
155
- const problems = []
156
-
157
- for (const [id, advisory] of advisories) {
158
- if (EXEMPTIONS.includes(id)) continue
159
- problems.push(
160
- `new dev-tree advisory ${id} (${advisory.severity}): ${advisory.title} - ` +
161
- `packages: ${[...new Set(advisory.packages)].sort().join(', ')}. ` +
162
- 'Fix it, or add it to EXEMPTIONS and name it in the build.yml comment.'
163
- )
164
- }
165
-
166
- for (const id of EXEMPTIONS) {
167
- if (advisories.has(id)) continue
168
- problems.push(
169
- `exempted advisory ${id} no longer appears in the tree - the exemption is ` +
170
- 'dead weight. Remove it from EXEMPTIONS and from the build.yml comment, ' +
171
- 'and restore the full `npm audit` gate.'
172
- )
173
- }
174
-
175
- for (const { name, newestKnown } of WATCHED_FOR_RELEASE) {
176
- const latest = latestPublished(name)
177
- if (latest === null) {
178
- console.log(`skipped the registry check for ${name} - npm view failed`)
179
- continue
180
- }
181
- if (isNewer(latest, newestKnown)) {
182
- problems.push(
183
- `${name} has published ${latest}, newer than the ${newestKnown} recorded ` +
184
- 'with the exemption - a fix may exist. Re-evaluate the exemption, bump ' +
185
- 'the chain if it clears, and drop the exemption when it does.'
186
- )
187
- }
188
- }
189
-
190
- const buildWorkflow = readFileSync(path.join(ROOT, BUILD_WORKFLOW), 'utf8')
191
- for (const id of EXEMPTIONS) {
192
- if (buildWorkflow.includes(id)) continue
193
- problems.push(
194
- `exemption ${id} is not named in ${BUILD_WORKFLOW} - the gate and this ` +
195
- 'script disagree about what is exempt.'
196
- )
197
- }
198
-
199
- const { moderate = 0, high = 0, critical = 0, total = 0 } =
200
- report?.metadata?.vulnerabilities ?? {}
201
-
202
- const summary = [
203
- '## Dev-tree audit watch',
204
- '',
205
- `Full audit: ${total} advisories (${critical} critical, ${high} high, ${moderate} moderate).`,
206
- '',
207
- '| advisory | severity | packages | exempt |',
208
- '| --- | --- | --- | --- |',
209
- ...[...advisories].map(
210
- ([id, advisory]) =>
211
- `| ${id} | ${advisory.severity} | ${[...new Set(advisory.packages)].sort().join(', ')} | ` +
212
- `${EXEMPTIONS.includes(id) ? 'yes' : '**no**'} |`
213
- ),
214
- '',
215
- problems.length
216
- ? ['### The exemption needs attention', '', ...problems.map((p) => `- ${p}`), ''].join('\n')
217
- : 'The exemption still matches the tree - nothing to do.',
218
- ''
219
- ].join('\n')
220
-
221
- console.log(summary)
222
- writeReport(summary)
223
-
224
- process.exitCode = problems.length ? 1 : 0
225
- }
226
-
227
- try {
228
- main()
229
- } catch (error) {
230
- // The watch itself broke, so the exemption went unchecked. Say so in the
231
- // report rather than leaving the workflow to fail on a missing file.
232
- const summary = [
233
- '## Dev-tree audit watch',
234
- '',
235
- 'The watch did not complete, so the dev-tree audit exemption was **not**',
236
- 'checked against the tree. Nothing is known about the advisories right now.',
237
- '',
238
- '```',
239
- error.message,
240
- '```',
241
- '',
242
- 'Investigate the failing step, then re-run the workflow.',
243
- ''
244
- ].join('\n')
245
-
246
- console.error(`audit watch failed: ${error.message}`)
247
- writeReport(summary)
248
- process.exit(1)
249
- }