@fabasoad/sarif-to-slack 0.2.4 → 1.0.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.
Files changed (193) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/.github/workflows/security.yml +0 -1
  3. package/.github/workflows/send-sarif-to-slack.yml +148 -76
  4. package/.gitleaksignore +8 -0
  5. package/.pre-commit-config.yaml +3 -3
  6. package/.tool-versions +1 -1
  7. package/dist/Logger.js +4 -1
  8. package/dist/SarifToSlackClient.d.ts +33 -0
  9. package/dist/SarifToSlackClient.d.ts.map +1 -0
  10. package/dist/SarifToSlackClient.js +178 -0
  11. package/dist/SlackMessageBuilder.js +34 -82
  12. package/dist/System.d.ts +2 -0
  13. package/dist/System.d.ts.map +1 -0
  14. package/dist/System.js +15 -0
  15. package/dist/index.cjs +843 -467
  16. package/dist/index.d.ts +35 -12
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +36 -12
  19. package/dist/model/Color.d.ts +70 -0
  20. package/dist/model/Color.d.ts.map +1 -0
  21. package/dist/model/Color.js +119 -0
  22. package/dist/model/Finding.d.ts +2 -0
  23. package/dist/model/Finding.d.ts.map +1 -0
  24. package/dist/model/Finding.js +93 -0
  25. package/dist/model/FindingsArray.d.ts +2 -0
  26. package/dist/model/FindingsArray.d.ts.map +1 -0
  27. package/dist/model/FindingsArray.js +24 -0
  28. package/dist/processors/CodeQLProcessor.d.ts +2 -0
  29. package/dist/processors/CodeQLProcessor.d.ts.map +1 -0
  30. package/dist/processors/CodeQLProcessor.js +17 -0
  31. package/dist/processors/CommonProcessor.d.ts +2 -0
  32. package/dist/processors/CommonProcessor.d.ts.map +1 -0
  33. package/dist/processors/CommonProcessor.js +84 -0
  34. package/dist/processors/ProcessorFactory.d.ts +2 -0
  35. package/dist/processors/ProcessorFactory.d.ts.map +1 -0
  36. package/dist/processors/ProcessorFactory.js +22 -0
  37. package/dist/processors/SnykProcessor.d.ts +2 -0
  38. package/dist/processors/SnykProcessor.d.ts.map +1 -0
  39. package/dist/processors/SnykProcessor.js +18 -0
  40. package/dist/representations/CompactGroupByRepresentation.d.ts +2 -0
  41. package/dist/representations/CompactGroupByRepresentation.d.ts.map +1 -0
  42. package/dist/representations/CompactGroupByRepresentation.js +58 -0
  43. package/dist/representations/CompactGroupByRunPerLevelRepresentation.d.ts +2 -0
  44. package/dist/representations/CompactGroupByRunPerLevelRepresentation.d.ts.map +1 -0
  45. package/dist/representations/CompactGroupByRunPerLevelRepresentation.js +13 -0
  46. package/dist/representations/CompactGroupByRunPerSeverityRepresentation.d.ts +2 -0
  47. package/dist/representations/CompactGroupByRunPerSeverityRepresentation.d.ts.map +1 -0
  48. package/dist/representations/CompactGroupByRunPerSeverityRepresentation.js +13 -0
  49. package/dist/representations/CompactGroupByRunRepresentation.d.ts +2 -0
  50. package/dist/representations/CompactGroupByRunRepresentation.d.ts.map +1 -0
  51. package/dist/representations/CompactGroupByRunRepresentation.js +39 -0
  52. package/dist/representations/CompactGroupBySarifPerLevelRepresentation.d.ts +2 -0
  53. package/dist/representations/CompactGroupBySarifPerLevelRepresentation.d.ts.map +1 -0
  54. package/dist/representations/CompactGroupBySarifPerLevelRepresentation.js +13 -0
  55. package/dist/representations/CompactGroupBySarifPerSeverityRepresentation.d.ts +2 -0
  56. package/dist/representations/CompactGroupBySarifPerSeverityRepresentation.d.ts.map +1 -0
  57. package/dist/representations/CompactGroupBySarifPerSeverityRepresentation.js +13 -0
  58. package/dist/representations/CompactGroupBySarifRepresentation.d.ts +2 -0
  59. package/dist/representations/CompactGroupBySarifRepresentation.d.ts.map +1 -0
  60. package/dist/representations/CompactGroupBySarifRepresentation.js +40 -0
  61. package/dist/representations/CompactGroupByToolNamePerLevelRepresentation.d.ts +2 -0
  62. package/dist/representations/CompactGroupByToolNamePerLevelRepresentation.d.ts.map +1 -0
  63. package/dist/representations/CompactGroupByToolNamePerLevelRepresentation.js +13 -0
  64. package/dist/representations/CompactGroupByToolNamePerSeverityRepresentation.d.ts +2 -0
  65. package/dist/representations/CompactGroupByToolNamePerSeverityRepresentation.d.ts.map +1 -0
  66. package/dist/representations/CompactGroupByToolNamePerSeverityRepresentation.js +13 -0
  67. package/dist/representations/CompactGroupByToolNameRepresentation.d.ts +2 -0
  68. package/dist/representations/CompactGroupByToolNameRepresentation.d.ts.map +1 -0
  69. package/dist/representations/CompactGroupByToolNameRepresentation.js +39 -0
  70. package/dist/representations/CompactTotalPerLevelRepresentation.d.ts +2 -0
  71. package/dist/representations/CompactTotalPerLevelRepresentation.d.ts.map +1 -0
  72. package/dist/representations/CompactTotalPerLevelRepresentation.js +13 -0
  73. package/dist/representations/CompactTotalPerSeverityRepresentation.d.ts +2 -0
  74. package/dist/representations/CompactTotalPerSeverityRepresentation.d.ts.map +1 -0
  75. package/dist/representations/CompactTotalPerSeverityRepresentation.js +13 -0
  76. package/dist/representations/CompactTotalRepresentation.d.ts +2 -0
  77. package/dist/representations/CompactTotalRepresentation.d.ts.map +1 -0
  78. package/dist/representations/CompactTotalRepresentation.js +25 -0
  79. package/dist/representations/Representation.d.ts +2 -0
  80. package/dist/representations/Representation.d.ts.map +1 -0
  81. package/dist/representations/Representation.js +28 -0
  82. package/dist/representations/RepresentationFactory.d.ts +2 -0
  83. package/dist/representations/RepresentationFactory.d.ts.map +1 -0
  84. package/dist/representations/RepresentationFactory.js +37 -0
  85. package/dist/sarif-to-slack.d.ts +337 -85
  86. package/dist/tsdoc-metadata.json +1 -1
  87. package/dist/types.d.ts +215 -51
  88. package/dist/types.d.ts.map +1 -1
  89. package/dist/types.js +225 -33
  90. package/dist/utils/Comparators.d.ts +2 -0
  91. package/dist/utils/Comparators.d.ts.map +1 -0
  92. package/dist/utils/Comparators.js +18 -0
  93. package/dist/utils/ExtendedArray.d.ts +2 -0
  94. package/dist/utils/ExtendedArray.d.ts.map +1 -0
  95. package/dist/utils/ExtendedArray.js +11 -0
  96. package/dist/utils/FileUtils.d.ts +2 -0
  97. package/dist/utils/FileUtils.d.ts.map +1 -0
  98. package/dist/utils/FileUtils.js +51 -0
  99. package/dist/utils/SarifUtils.js +19 -53
  100. package/etc/sarif-to-slack.api.md +161 -99
  101. package/jest.config.json +2 -2
  102. package/package.json +9 -9
  103. package/scripts/save-metadata.sh +15 -0
  104. package/src/Logger.ts +4 -0
  105. package/src/SarifToSlackClient.ts +202 -0
  106. package/src/SlackMessageBuilder.ts +35 -115
  107. package/src/System.ts +16 -0
  108. package/src/index.ts +47 -20
  109. package/src/model/Color.ts +201 -0
  110. package/src/model/Finding.ts +137 -0
  111. package/src/model/FindingsArray.ts +27 -0
  112. package/src/processors/CodeQLProcessor.ts +19 -0
  113. package/src/processors/CommonProcessor.ts +103 -0
  114. package/src/processors/ProcessorFactory.ts +23 -0
  115. package/src/processors/SnykProcessor.ts +19 -0
  116. package/src/representations/CompactGroupByRepresentation.ts +67 -0
  117. package/src/representations/CompactGroupByRunPerLevelRepresentation.ts +14 -0
  118. package/src/representations/CompactGroupByRunPerSeverityRepresentation.ts +14 -0
  119. package/src/representations/CompactGroupByRunRepresentation.ts +44 -0
  120. package/src/representations/CompactGroupBySarifPerLevelRepresentation.ts +15 -0
  121. package/src/representations/CompactGroupBySarifPerSeverityRepresentation.ts +15 -0
  122. package/src/representations/CompactGroupBySarifRepresentation.ts +45 -0
  123. package/src/representations/CompactGroupByToolNamePerLevelRepresentation.ts +15 -0
  124. package/src/representations/CompactGroupByToolNamePerSeverityRepresentation.ts +15 -0
  125. package/src/representations/CompactGroupByToolNameRepresentation.ts +44 -0
  126. package/src/representations/CompactTotalPerLevelRepresentation.ts +14 -0
  127. package/src/representations/CompactTotalPerSeverityRepresentation.ts +14 -0
  128. package/src/representations/CompactTotalRepresentation.ts +27 -0
  129. package/src/representations/Representation.ts +35 -0
  130. package/src/representations/RepresentationFactory.ts +49 -0
  131. package/src/types.ts +270 -53
  132. package/src/utils/Comparators.ts +19 -0
  133. package/src/utils/ExtendedArray.ts +11 -0
  134. package/src/utils/FileUtils.ts +60 -0
  135. package/src/utils/SarifUtils.ts +19 -71
  136. package/test-data/sarif/codeql-python.sarif +1448 -1
  137. package/test-data/sarif/codeql-typescript.sarif +3474 -1
  138. package/test-data/sarif/grype-github-actions.sarif +65 -0
  139. package/test-data/sarif/osv-scanner-composer.sarif +972 -0
  140. package/test-data/sarif/osv-scanner-container.sarif +2278 -0
  141. package/test-data/sarif/osv-scanner-gomodules.sarif +813 -0
  142. package/test-data/sarif/osv-scanner-hex.sarif +147 -0
  143. package/test-data/sarif/osv-scanner-maven.sarif +171 -0
  144. package/test-data/sarif/osv-scanner-npm.sarif +627 -0
  145. package/test-data/sarif/osv-scanner-pip.sarif +206 -0
  146. package/test-data/sarif/osv-scanner-pipenv.sarif +243 -0
  147. package/test-data/sarif/osv-scanner-pnpm.sarif +174 -0
  148. package/test-data/sarif/osv-scanner-poetry.sarif +1893 -0
  149. package/test-data/sarif/osv-scanner-rubygems.sarif +402 -0
  150. package/test-data/sarif/osv-scanner-uv.sarif +206 -0
  151. package/test-data/sarif/osv-scanner-yarn.sarif +5207 -0
  152. package/test-data/sarif/runs-0.sarif +5 -0
  153. package/test-data/sarif/runs-2-tools-2-results-0.sarif +1 -1
  154. package/test-data/sarif/runs-2-tools-2.sarif +1 -1
  155. package/test-data/sarif/runs-3-tools-2-results-0.sarif +1 -1
  156. package/test-data/sarif/runs-3-tools-2.sarif +1 -1
  157. package/test-data/sarif/tmp/codeql-csharp.sarif +1 -0
  158. package/test-data/sarif/tmp/grype-container.sarif +1774 -0
  159. package/test-data/sarif/tmp/runs-1-tools-1-results-0.sarif +18 -0
  160. package/test-data/sarif/tmp/runs-2-tools-2.sarif +686 -0
  161. package/test-data/sarif/trivy-iac.sarif +1 -1
  162. package/tests/integration/SendSarifToSlack.spec.ts +95 -27
  163. package/tsconfig.json +2 -0
  164. package/dist/Processors.d.ts +0 -2
  165. package/dist/Processors.d.ts.map +0 -1
  166. package/dist/Processors.js +0 -61
  167. package/dist/SarifToSlackService.d.ts +0 -39
  168. package/dist/SarifToSlackService.d.ts.map +0 -1
  169. package/dist/SarifToSlackService.js +0 -102
  170. package/dist/model/SarifModelPerRun.d.ts +0 -2
  171. package/dist/model/SarifModelPerRun.d.ts.map +0 -1
  172. package/dist/model/SarifModelPerRun.js +0 -90
  173. package/dist/model/SarifModelPerSarif.d.ts +0 -2
  174. package/dist/model/SarifModelPerSarif.d.ts.map +0 -1
  175. package/dist/model/SarifModelPerSarif.js +0 -102
  176. package/dist/model/types.d.ts +0 -2
  177. package/dist/model/types.d.ts.map +0 -1
  178. package/dist/model/types.js +0 -49
  179. package/dist/utils/SortUtils.d.ts +0 -2
  180. package/dist/utils/SortUtils.d.ts.map +0 -1
  181. package/dist/utils/SortUtils.js +0 -20
  182. package/dist/version.d.ts +0 -2
  183. package/dist/version.d.ts.map +0 -1
  184. package/dist/version.js +0 -11
  185. package/scripts/save-version.sh +0 -13
  186. package/src/Processors.ts +0 -68
  187. package/src/SarifToSlackService.ts +0 -115
  188. package/src/model/SarifModelPerRun.ts +0 -123
  189. package/src/model/SarifModelPerSarif.ts +0 -126
  190. package/src/model/types.ts +0 -50
  191. package/src/utils/SortUtils.ts +0 -33
  192. package/src/version.ts +0 -10
  193. package/tests/Processors.spec.ts +0 -76
@@ -0,0 +1,15 @@
1
+ import CompactGroupBySarifRepresentation
2
+ from './CompactGroupBySarifRepresentation'
3
+
4
+ /**
5
+ * Since {@link CompactGroupBySarifRepresentation} is an abstract class, the only
6
+ * question that this class should "answer" is what property should be used in
7
+ * the compact representation. In this case it is "severity".
8
+ * @internal
9
+ */
10
+ export default class CompactGroupBySarifPerSeverityRepresentation extends CompactGroupBySarifRepresentation {
11
+
12
+ public override compose(): string {
13
+ return this.composeByProperty('severity')
14
+ }
15
+ }
@@ -0,0 +1,45 @@
1
+ import path from 'node:path'
2
+ import { Finding } from '../model/Finding'
3
+ import CompactGroupByRepresentation from './CompactGroupByRepresentation'
4
+ import { SarifModel } from '../types'
5
+
6
+ /**
7
+ * Since {@link CompactGroupByRepresentation} already prepares compact representation
8
+ * of findings, this class defines a grouping rule. In this case it groups
9
+ * findings by SARIF file. Every SARIF file will be grouped separately, such as:
10
+ * @example
11
+ * ```text
12
+ * grype-results-01.sarif
13
+ * Error: 1, Warning: 4
14
+ * grype-results-02.sarif
15
+ * Warning: 1, Note: 20
16
+ * ```
17
+ * @internal
18
+ * It is an abstract class, so the only question that derived classes should
19
+ * "answer" is what property should be used in the compact representation, such
20
+ * as "level" and "severity".
21
+ */
22
+ export default abstract class CompactGroupBySarifRepresentation extends CompactGroupByRepresentation {
23
+
24
+ public constructor(model: SarifModel) {
25
+ super(model, 'sarifPath')
26
+ }
27
+
28
+ protected override groupFindings(): Map<string, Finding[]> {
29
+ const result = new Map<string, Finding[]>()
30
+ for (let index = 0; index < this._model.sarifFiles.length; index++) {
31
+ const key: string = this.composeGroupTitle(this._model.sarifFiles[index], index)
32
+ if (result.get(key) == null) {
33
+ result.set(key, [])
34
+ }
35
+ this._model.findings
36
+ .filter((f: Finding): boolean => f.sarifPath === this._model.sarifFiles[index])
37
+ .forEach((f: Finding) => result.get(key)?.push(f))
38
+ }
39
+ return result
40
+ }
41
+
42
+ private composeGroupTitle(sarifPath: string, index: number): string {
43
+ return `${this.italic(`[File ${index + 1}]`)} ${this.bold(path.basename(sarifPath))}`
44
+ }
45
+ }
@@ -0,0 +1,15 @@
1
+ import CompactGroupByToolNameRepresentation
2
+ from './CompactGroupByToolNameRepresentation'
3
+
4
+ /**
5
+ * Since {@link CompactGroupByToolNameRepresentation} is an abstract class, the
6
+ * only question that this class should "answer" is what property should be used
7
+ * in the compact representation. In this case it is "level".
8
+ * @internal
9
+ */
10
+ export default class CompactGroupByToolNamePerLevelRepresentation extends CompactGroupByToolNameRepresentation {
11
+
12
+ public override compose(): string {
13
+ return this.composeByProperty('level')
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ import CompactGroupByToolNameRepresentation
2
+ from './CompactGroupByToolNameRepresentation'
3
+
4
+ /**
5
+ * Since {@link CompactGroupByToolNameRepresentation} is an abstract class, the
6
+ * only question that this class should "answer" is what property should be used
7
+ * in the compact representation. In this case it is "severity".
8
+ * @internal
9
+ */
10
+ export default class CompactGroupByToolNamePerSeverityRepresentation extends CompactGroupByToolNameRepresentation {
11
+
12
+ public override compose(): string {
13
+ return this.composeByProperty('severity')
14
+ }
15
+ }
@@ -0,0 +1,44 @@
1
+ import { Finding } from '../model/Finding'
2
+ import CompactGroupByRepresentation from './CompactGroupByRepresentation'
3
+ import { SarifModel } from '../types'
4
+
5
+ /**
6
+ * Since {@link CompactGroupByRepresentation} already prepares compact representation
7
+ * of findings, this class defines a grouping rule. In this case it groups
8
+ * findings by tool name. Every tool name will be grouped separately, such as:
9
+ * @example
10
+ * ```text
11
+ * Grype
12
+ * Error: 1, Warning: 4
13
+ * Trivy
14
+ * Warning: 1, Note: 20
15
+ * ```
16
+ * @internal
17
+ * It is an abstract class, so the only question that derived classes should
18
+ * "answer" is what property should be used in the compact representation, such
19
+ * as "level" and "severity".
20
+ */
21
+ export default abstract class CompactGroupByToolNameRepresentation extends CompactGroupByRepresentation {
22
+
23
+ public constructor(model: SarifModel) {
24
+ super(model, 'toolName')
25
+ }
26
+
27
+ protected override groupFindings(): Map<string, Finding[]> {
28
+ const result = new Map<string, Finding[]>()
29
+ for (const run of this._model.runs) {
30
+ const key: string = this.composeGroupTitle(run.toolName)
31
+ if (result.get(key) == null) {
32
+ result.set(key, [])
33
+ }
34
+ this._model.findings
35
+ .filter((f: Finding): boolean => f.runId === run.id)
36
+ .forEach((f: Finding) => result.get(key)?.push(f))
37
+ }
38
+ return result
39
+ }
40
+
41
+ private composeGroupTitle(toolName: string): string {
42
+ return this.bold(toolName)
43
+ }
44
+ }
@@ -0,0 +1,14 @@
1
+ import CompactTotalRepresentation from './CompactTotalRepresentation'
2
+
3
+ /**
4
+ * Since {@link CompactTotalRepresentation} is an abstract class, the only
5
+ * question that this class should "answer" is what property should be used in
6
+ * the compact representation. In this case it is "level".
7
+ * @internal
8
+ */
9
+ export default class CompactTotalPerLevelRepresentation extends CompactTotalRepresentation {
10
+
11
+ public override compose(): string {
12
+ return this.composeByProperty('level')
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ import CompactTotalRepresentation from './CompactTotalRepresentation'
2
+
3
+ /**
4
+ * Since {@link CompactTotalRepresentation} is an abstract class, the only
5
+ * question that this class should "answer" is what property should be used in
6
+ * the compact representation. In this case it is "severity".
7
+ * @internal
8
+ */
9
+ export default class CompactTotalPerSeverityRepresentation extends CompactTotalRepresentation {
10
+
11
+ public override compose(): string {
12
+ return this.composeByProperty('severity')
13
+ }
14
+ }
@@ -0,0 +1,27 @@
1
+ import CompactGroupByRepresentation from './CompactGroupByRepresentation'
2
+ import { Finding } from '../model/Finding'
3
+
4
+ /**
5
+ * Since {@link CompactGroupByRepresentation} already prepares compact representation
6
+ * of findings, this class defines a grouping rule. In this case it does not really
7
+ * group, but just add everything under the same "Total" group, such as:
8
+ * @example
9
+ * ```text
10
+ * Total
11
+ * Warning: 1, Note: 20
12
+ * ```
13
+ * @internal
14
+ * It is an abstract class, so the only question that derived classes should
15
+ * "answer" is what property should be used in the compact representation, such
16
+ * as "level" and "severity".
17
+ */
18
+ export default abstract class CompactTotalRepresentation extends CompactGroupByRepresentation {
19
+
20
+ protected override groupFindings(): Map<string, Finding[]> {
21
+ const result = new Map<string, Finding[]>()
22
+ if (this._model.findings.length > 0) {
23
+ result.set('Total', this._model.findings)
24
+ }
25
+ return result
26
+ }
27
+ }
@@ -0,0 +1,35 @@
1
+ import { SarifModel } from '../types'
2
+ import { Finding } from '../model/Finding'
3
+ import { findingsComparatorByKey } from '../utils/Comparators'
4
+ import FindingsArray from '../model/FindingsArray'
5
+
6
+ /**
7
+ * The most base abstract class for the representation. Every representation class
8
+ * must be derived from this class implicitly or explicitly.
9
+ * @internal
10
+ */
11
+ export default abstract class Representation {
12
+ protected readonly _model: SarifModel
13
+
14
+ public constructor(model: SarifModel, findingSortKey: keyof Finding = 'level') {
15
+ this._model = model
16
+ this._model.findings = model
17
+ .findings
18
+ .map((f: Finding): Finding => f.clone())
19
+ .sort(findingsComparatorByKey(findingSortKey))
20
+ .reduce((arr: FindingsArray, f: Finding): FindingsArray => {
21
+ arr.push(f)
22
+ return arr
23
+ }, new FindingsArray())
24
+ }
25
+
26
+ protected bold(text: string): string {
27
+ return `*${text}*`
28
+ }
29
+
30
+ protected italic(text: string): string {
31
+ return `_${text}_`
32
+ }
33
+
34
+ abstract compose(): string
35
+ }
@@ -0,0 +1,49 @@
1
+ import Representation from './Representation'
2
+ import { RepresentationType, SarifModel } from '../types'
3
+ import CompactGroupByRunPerLevelRepresentation
4
+ from './CompactGroupByRunPerLevelRepresentation'
5
+ import CompactGroupByRunPerSeverityRepresentation
6
+ from './CompactGroupByRunPerSeverityRepresentation'
7
+ import CompactGroupByToolNamePerLevelRepresentation
8
+ from './CompactGroupByToolNamePerLevelRepresentation'
9
+ import CompactGroupByToolNamePerSeverityRepresentation
10
+ from './CompactGroupByToolNamePerSeverityRepresentation'
11
+ import CompactGroupBySarifPerLevelRepresentation
12
+ from './CompactGroupBySarifPerLevelRepresentation'
13
+ import CompactGroupBySarifPerSeverityRepresentation
14
+ from './CompactGroupBySarifPerSeverityRepresentation'
15
+ import CompactTotalPerSeverityRepresentation
16
+ from './CompactTotalPerSeverityRepresentation'
17
+ import CompactTotalPerLevelRepresentation
18
+ from './CompactTotalPerLevelRepresentation'
19
+
20
+ /**
21
+ * Factory class that creates a {@link Representation} class based on the provided
22
+ * {@link RepresentationType}.
23
+ * @internal
24
+ */
25
+ export function createRepresentation(
26
+ model: SarifModel,
27
+ type: RepresentationType = RepresentationType.CompactGroupByToolNamePerSeverity
28
+ ): Representation {
29
+ switch (type) {
30
+ case RepresentationType.CompactGroupByRunPerLevel:
31
+ return new CompactGroupByRunPerLevelRepresentation(model)
32
+ case RepresentationType.CompactGroupByRunPerSeverity:
33
+ return new CompactGroupByRunPerSeverityRepresentation(model)
34
+ case RepresentationType.CompactGroupByToolNamePerLevel:
35
+ return new CompactGroupByToolNamePerLevelRepresentation(model)
36
+ case RepresentationType.CompactGroupByToolNamePerSeverity:
37
+ return new CompactGroupByToolNamePerSeverityRepresentation(model)
38
+ case RepresentationType.CompactGroupBySarifPerLevel:
39
+ return new CompactGroupBySarifPerLevelRepresentation(model)
40
+ case RepresentationType.CompactGroupBySarifPerSeverity:
41
+ return new CompactGroupBySarifPerSeverityRepresentation(model)
42
+ case RepresentationType.CompactTotalPerLevel:
43
+ return new CompactTotalPerLevelRepresentation(model)
44
+ case RepresentationType.CompactTotalPerSeverity:
45
+ return new CompactTotalPerSeverityRepresentation(model)
46
+ default:
47
+ throw new Error(`Unknown representation type: ${type}`)
48
+ }
49
+ }
package/src/types.ts CHANGED
@@ -1,10 +1,6 @@
1
- import type { Log } from 'sarif'
2
-
3
- /**
4
- * Type representing a SARIF log.
5
- * @public
6
- */
7
- export type SarifLog = Log
1
+ import { Run } from 'sarif'
2
+ import { Color, ColorOptions } from './model/Color'
3
+ import FindingsArray from './model/FindingsArray'
8
4
 
9
5
  /**
10
6
  * Interface for a Slack message that can be sent.
@@ -16,10 +12,10 @@ export interface SlackMessage {
16
12
  * @returns A promise that resolves to the response from the Slack webhook.
17
13
  */
18
14
  send: () => Promise<string>
19
- /**
20
- * The SARIF log associated with this Slack message.
21
- */
22
- sarif: SarifLog
15
+ withActor(actor?: string): void
16
+ withFooter(text?: string, type?: FooterType): void
17
+ withHeader(header?: string): void
18
+ withRun(): void
23
19
  }
24
20
 
25
21
  /**
@@ -107,86 +103,307 @@ export type FooterOptions = IncludeAwareWithValueOptions & {
107
103
  }
108
104
 
109
105
  /**
110
- * Enum representing how to group results.
106
+ * This represents what type of message should be sent. There are various options
107
+ * to show information from SARIF in Slack message.
111
108
  * @public
112
109
  */
113
- export enum GroupResultsBy {
110
+ export enum RepresentationType {
111
+ /**
112
+ * Compact information about findings grouped by Run with the level representation.
113
+ * @example
114
+ * ```text
115
+ * [Run 1] Grype
116
+ * Error: 1, Warning: 4
117
+ * [Run 2] Grype
118
+ * Warning: 1, Note: 20
119
+ * ```
120
+ */
121
+ CompactGroupByRunPerLevel = 0,
122
+ /**
123
+ * Compact information about findings grouped by Run with the severity representation.
124
+ * @example
125
+ * ```text
126
+ * [Run 1] Grype
127
+ * Critical: 1, High: 3, Medium: 1
128
+ * [Run 2] Grype
129
+ * Medium: 1, Low: 20
130
+ * ```
131
+ */
132
+ CompactGroupByRunPerSeverity = 1,
133
+ /**
134
+ * Compact information about findings grouped by tool name with the level representation.
135
+ * @example
136
+ * ```text
137
+ * Grype
138
+ * Error: 1, Warning: 5, Note: 20
139
+ * ```
140
+ */
141
+ CompactGroupByToolNamePerLevel = 2,
114
142
  /**
115
- * Groups results by the tool name. Particularly, groups by the runs[].tool.driver.name
116
- * property from the SARIF file(s).
143
+ * Compact information about findings grouped by tool name with the severity representation.
144
+ * @example
145
+ * ```text
146
+ * Grype
147
+ * Critical: 1, High: 3, Medium: 2, Low: 20
148
+ * ```
117
149
  */
118
- ToolName = 0,
150
+ CompactGroupByToolNamePerSeverity = 3,
119
151
  /**
120
- * Groups results by the run. It provides the result from each run individually.
152
+ * Compact information about findings grouped by SARIF file with the level representation.
153
+ * @example
154
+ * ```text
155
+ * grype-results-01.sarif
156
+ * Error: 1, Warning: 2, Note: 1
157
+ * grype-results-02.sarif
158
+ * Warning: 3, Note: 19
159
+ * ```
121
160
  */
122
- Run = 1,
161
+ CompactGroupBySarifPerLevel = 4,
123
162
  /**
124
- * Does not group results. It provides the result from all the runs from all
125
- * the provided SARIF files.
163
+ * Compact information about findings grouped by SARIF file with the severity
164
+ * representation.
165
+ * @example
166
+ * ```text
167
+ * grype-results-01.sarif
168
+ * High: 3, Medium: 1, Low: 11
169
+ * grype-results-02.sarif
170
+ * Critical: 1, Medium: 1, Low: 9
171
+ * ```
126
172
  */
127
- Total = 2,
173
+ CompactGroupBySarifPerSeverity = 5,
174
+ /**
175
+ * Compact information about findings with the level representation.
176
+ * @example
177
+ * ```text
178
+ * Total
179
+ * Error: 1, Warning: 5, Note: 20
180
+ * ```
181
+ */
182
+ CompactTotalPerLevel = 6,
183
+ /**
184
+ * Compact information about findings with the severity representation.
185
+ * @example
186
+ * ```text
187
+ * Total
188
+ * Critical: 1, High: 3, Medium: 2, Low: 20
189
+ * ```
190
+ */
191
+ CompactTotalPerSeverity = 7,
128
192
  }
129
193
 
130
194
  /**
131
- * Enum representing how to calculate results.
195
+ * Options for logging.
132
196
  * @public
133
197
  */
134
- export enum CalculateResultsBy {
135
- /**
136
- * Calculates results by the security level of the findings: Error, Warning,
137
- * Note and Unknown. At first, it tries to get the security level from runs[].results[].level
138
- * property. If it is not defined, it tries to get the security level from the
139
- * respective rule of each result, using the rules[].properties['problem.severity']
140
- * property.
141
- */
142
- Level = 0,
198
+ export type LogOptions = {
199
+ level?: LogLevel,
143
200
  /**
144
- * Calculates results by the security severity of the findings: Critical, High,
145
- * Medium, Low, None and Unknown. it tries to get the security severity from the
146
- * respective rule of each result, using the rules[].properties['security-severity']
147
- * property. This property contains CVSS score, which is then mapped to the
148
- * security severity value.
201
+ * More details here: https://github.com/fullstack-build/tslog?tab=readme-ov-file#pretty-templates-and-styles-color-settings
149
202
  */
150
- Severity = 1,
203
+ template?: string,
204
+ colored?: boolean,
151
205
  }
152
206
 
153
207
  /**
154
- * Options for how to output the results in the Slack message.
208
+ * SARIF file extension.
155
209
  * @public
156
210
  */
157
- export type SarifToSlackOutput = {
158
- groupBy?: GroupResultsBy,
159
- calculateBy?: CalculateResultsBy,
211
+ export type SarifFileExtension = 'sarif' | 'json'
212
+
213
+ /**
214
+ * Represents options for the provided SARIF file(s), such as path, should files
215
+ * from this path be retrieved recursively or not, and file extension.
216
+ * @public
217
+ */
218
+ export type SarifOptions = {
219
+ path: string,
220
+ recursive?: boolean,
221
+ extension?: SarifFileExtension,
160
222
  }
161
223
 
162
224
  /**
163
- * Options for logging.
225
+ * This enum represents the condition on when message should be sent. If this
226
+ * condition is satisfied then message is sent, otherwise - message is not sent.
164
227
  * @public
165
228
  */
166
- export type LogOptions = {
167
- level?: LogLevel,
229
+ export enum SendIf {
168
230
  /**
169
- * More details here: https://github.com/fullstack-build/tslog?tab=readme-ov-file#pretty-templates-and-styles-color-settings
231
+ * Send message only if there is at least one finding with "Critical" severity.
232
+ * Since it is the higher possible severity, it is the same as "Critical" or
233
+ * higher.
170
234
  */
171
- template?: string,
172
- colored?: boolean,
235
+ SeverityCritical,
236
+ /**
237
+ * Send message only if there is at least one finding with "High" severity.
238
+ */
239
+ SeverityHigh,
240
+ /**
241
+ * Send message only if there is at least one finding with "High" severity or
242
+ * higher, that includes "High" and "Critical".
243
+ */
244
+ SeverityHighOrHigher,
245
+ /**
246
+ * Send message only if there is at least one finding with "Medium" severity.
247
+ */
248
+ SeverityMedium,
249
+ /**
250
+ * Send message only if there is at least one finding with "Medium" severity
251
+ * or higher, that includes "Medium", "High" and "Critical".
252
+ */
253
+ SeverityMediumOrHigher,
254
+ /**
255
+ * Send message only if there is at least one finding with "Low" severity.
256
+ */
257
+ SeverityLow,
258
+ /**
259
+ * Send message only if there is at least one finding with "Low" severity or
260
+ * higher, that includes "Low", "Medium", "High" and "Critical".
261
+ */
262
+ SeverityLowOrHigher,
263
+ /**
264
+ * Send message only if there is at least one finding with "None" severity.
265
+ */
266
+ SeverityNone,
267
+ /**
268
+ * Send message only if there is at least one finding with "None" severity or
269
+ * higher, that includes "None", "Low", "Medium", "High" and "Critical".
270
+ */
271
+ SeverityNoneOrHigher,
272
+ /**
273
+ * Send message only if there is at least one finding with "Unknown" severity.
274
+ */
275
+ SeverityUnknown,
276
+ /**
277
+ * Send message only if there is at least one finding with "Unknown" severity
278
+ * or higher, that includes "Unknown", "None", "Low", "Medium", "High" and "Critical".
279
+ */
280
+ SeverityUnknownOrHigher,
281
+ /**
282
+ * Send message only if there is at least one finding with "Error" level.
283
+ * Since it is the higher possible level, it is the same as "Error" or higher.
284
+ */
285
+ LevelError,
286
+ /**
287
+ * Send message only if there is at least one finding with "Warning" level.
288
+ */
289
+ LevelWarning,
290
+ /**
291
+ * Send message only if there is at least one finding with "Warning" level or
292
+ * higher, that includes "Warning" and "Error".
293
+ */
294
+ LevelWarningOrHigher,
295
+ /**
296
+ * Send message only if there is at least one finding with "Note" level.
297
+ */
298
+ LevelNote,
299
+ /**
300
+ * Send message only if there is at least one finding with "Note" level or
301
+ * higher, that includes "Note", "Warning" and "Error.
302
+ */
303
+ LevelNoteOrHigher,
304
+ /**
305
+ * Send message only if there is at least one finding with "None" level.
306
+ */
307
+ LevelNone,
308
+ /**
309
+ * Send message only if there is at least one finding with "None" level or
310
+ * higher, that includes "None", "Note", "Warning" and "Error.
311
+ */
312
+ LevelNoneOrHigher,
313
+ /**
314
+ * Send message only if there is at least one finding with "Unknown" level.
315
+ */
316
+ LevelUnknown,
317
+ /**
318
+ * Send message only if there is at least one finding with "Unknown" level or
319
+ * higher, that includes "Unknown", "None", "Note", "Warning" and "Error.
320
+ */
321
+ LevelUnknownOrHigher,
322
+ /**
323
+ * Always send a message.
324
+ */
325
+ Always,
326
+ /**
327
+ * Send a message if at least 1 vulnerability is found.
328
+ */
329
+ Some,
330
+ /**
331
+ * Send a message only if no vulnerabilities are found.
332
+ */
333
+ Empty,
334
+ /**
335
+ * Never send a message.
336
+ */
337
+ Never,
173
338
  }
174
339
 
175
340
  /**
176
- * Options for the SarifToSlackService.
341
+ * Options for the SarifToSlackClient.
177
342
  * @public
178
343
  */
179
- export type SarifToSlackServiceOptions = {
180
- // The Slack webhook URL to send messages to.
344
+ export type SarifToSlackClientOptions = {
181
345
  webhookUrl: string,
182
- sarifPath: string,
346
+ sarif: SarifOptions,
183
347
  username?: string,
184
348
  iconUrl?: string,
185
- color?: string,
349
+ color?: Color | ColorOptions,
186
350
  log?: LogOptions,
187
351
  header?: IncludeAwareWithValueOptions,
188
352
  footer?: FooterOptions,
189
353
  actor?: IncludeAwareWithValueOptions,
190
354
  run?: IncludeAwareOptions,
191
- output?: SarifToSlackOutput,
355
+ representation?: RepresentationType,
356
+ sendIf?: SendIf,
357
+ }
358
+
359
+ /**
360
+ * Enum of security severity.
361
+ * @privateRemarks Order should remain unchanged. It is used in multiple places,
362
+ * such as sorting in Slack message (more important come first) and to identify
363
+ * provided severity if it is requested severity or higher.
364
+ * @internal
365
+ */
366
+ export enum SecuritySeverity {
367
+ Unknown = 0,
368
+ None = 1,
369
+ Low = 2,
370
+ Medium = 3,
371
+ High = 4,
372
+ Critical = 5,
373
+ }
374
+
375
+ /**
376
+ * Enum of security level.
377
+ * @privateRemarks Order should remain unchanged. It is used in multiple places,
378
+ * such as sorting in Slack message (more important come first) and to identify
379
+ * provided level if it is requested level or higher.
380
+ * @internal
381
+ */
382
+ export enum SecurityLevel {
383
+ Unknown = 0,
384
+ None = 1,
385
+ Note = 2,
386
+ Warning = 3,
387
+ Error = 4,
388
+ }
389
+
390
+ /**
391
+ * The data about run, such as {@link Run} itself, tool name of the run and ID
392
+ * which is manually generated and unique within a single execution.
393
+ * @internal
394
+ */
395
+ export type RunData = {
396
+ id: number,
397
+ run: Run,
398
+ toolName: string,
399
+ }
400
+
401
+ /**
402
+ * Model that is used by {@link Representation}
403
+ * @internal
404
+ */
405
+ export type SarifModel = {
406
+ sarifFiles: string[],
407
+ runs: RunData[],
408
+ findings: FindingsArray,
192
409
  }
@@ -0,0 +1,19 @@
1
+ import { Finding } from '../model/Finding'
2
+
3
+ /**
4
+ * This function returns a comparator function based on the property of the
5
+ * {@link Finding} object.
6
+ * @param key Property name of the {@link Finding} object.
7
+ * @internal
8
+ */
9
+ export function findingsComparatorByKey<K extends keyof Finding>(key: K): (a: Finding, b: Finding) => number {
10
+ return (a: Finding, b: Finding): number => {
11
+ switch (key) {
12
+ case 'severity': return b.severity - a.severity
13
+ case 'level': return b.level - a.level
14
+ case 'runId': return a.runId - b.runId
15
+ case 'toolName': return a.toolName.toLowerCase().localeCompare(b.toolName.toLowerCase())
16
+ default: return 1
17
+ }
18
+ }
19
+ }