@fabasoad/sarif-to-slack 0.2.5 → 1.1.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 (192) 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 +145 -73
  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 +1 -3
  13. package/dist/System.d.ts.map +1 -1
  14. package/dist/System.js +10 -3
  15. package/dist/index.cjs +826 -472
  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 +80 -0
  20. package/dist/model/Color.d.ts.map +1 -0
  21. package/dist/model/Color.js +106 -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 +347 -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 +20 -54
  100. package/etc/sarif-to-slack.api.md +162 -99
  101. package/jest.config.json +2 -2
  102. package/package.json +7 -7
  103. package/scripts/save-metadata.sh +12 -10
  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 +9 -2
  108. package/src/index.ts +47 -20
  109. package/src/model/Color.ts +195 -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 +20 -72
  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 -104
  170. package/dist/metadata.d.ts +0 -2
  171. package/dist/metadata.d.ts.map +0 -1
  172. package/dist/metadata.js +0 -11
  173. package/dist/model/SarifModelPerRun.d.ts +0 -2
  174. package/dist/model/SarifModelPerRun.d.ts.map +0 -1
  175. package/dist/model/SarifModelPerRun.js +0 -90
  176. package/dist/model/SarifModelPerSarif.d.ts +0 -2
  177. package/dist/model/SarifModelPerSarif.d.ts.map +0 -1
  178. package/dist/model/SarifModelPerSarif.js +0 -102
  179. package/dist/model/types.d.ts +0 -2
  180. package/dist/model/types.d.ts.map +0 -1
  181. package/dist/model/types.js +0 -49
  182. package/dist/utils/SortUtils.d.ts +0 -2
  183. package/dist/utils/SortUtils.d.ts.map +0 -1
  184. package/dist/utils/SortUtils.js +0 -20
  185. package/src/Processors.ts +0 -68
  186. package/src/SarifToSlackService.ts +0 -117
  187. package/src/metadata.ts +0 -10
  188. package/src/model/SarifModelPerRun.ts +0 -120
  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/tests/Processors.spec.ts +0 -76
@@ -0,0 +1,202 @@
1
+ import { promises as fs } from 'fs'
2
+ import { Log } from 'sarif'
3
+ import Logger from './Logger'
4
+ import { SlackMessageBuilder } from './SlackMessageBuilder'
5
+ import {
6
+ LogOptions,
7
+ RunData,
8
+ SarifModel,
9
+ SarifOptions,
10
+ SarifToSlackClientOptions,
11
+ SecurityLevel,
12
+ SecuritySeverity,
13
+ SendIf,
14
+ SlackMessage
15
+ } from './types'
16
+ import System from './System'
17
+ import { extractListOfFiles } from './utils/FileUtils'
18
+ import { createRepresentation } from './representations/RepresentationFactory'
19
+ import { createFinding } from './model/Finding'
20
+ import { findToolComponent, findToolComponentDriver } from './utils/SarifUtils'
21
+ import { identifyColor } from './model/Color'
22
+ import FindingsArray from './model/FindingsArray'
23
+
24
+ /**
25
+ * Service to convert SARIF files to Slack messages and send them.
26
+ * @public
27
+ */
28
+ export class SarifToSlackClient {
29
+ private _message?: SlackMessage
30
+ private _sarifModel?: SarifModel
31
+
32
+ private _sendIf: SendIf = SendIf.Always
33
+
34
+ private constructor(log?: LogOptions) {
35
+ Logger.initialize(log)
36
+ System.initialize()
37
+ }
38
+
39
+ private static *createRunIdGenerator(): Generator<number> {
40
+ let runId: number = 1
41
+ while (true) {
42
+ yield runId++
43
+ }
44
+ }
45
+
46
+ public static async create(opts: SarifToSlackClientOptions): Promise<SarifToSlackClient> {
47
+ const instance = new SarifToSlackClient(opts.log)
48
+ Logger.trace('opts', opts)
49
+ instance._sendIf = opts.sendIf ?? instance._sendIf
50
+ instance._sarifModel = await SarifToSlackClient.buildModel(opts.sarif)
51
+ Logger.trace('instance._sarifModel', instance._sarifModel)
52
+ instance._message = await SarifToSlackClient.initialize(instance._sarifModel, opts)
53
+ Logger.trace('instance._message', instance._message)
54
+ return instance;
55
+ }
56
+
57
+ private static async buildModel(sarifOpts: SarifOptions): Promise<SarifModel> {
58
+ const sarifFiles: string[] = extractListOfFiles(sarifOpts)
59
+ if (sarifFiles.length === 0) {
60
+ throw new Error(`No SARIF files found at the provided path: ${sarifOpts.path}`)
61
+ }
62
+
63
+ const model: SarifModel = { sarifFiles, runs: [], findings: new FindingsArray() }
64
+ const runIdGenerator: Generator<number> = SarifToSlackClient.createRunIdGenerator()
65
+ for (const sarifPath of sarifFiles) {
66
+ const sarifJson: string = await fs.readFile(sarifPath, 'utf8')
67
+ const sarifLog: Log = JSON.parse(sarifJson) as Log
68
+
69
+ for (const run of sarifLog.runs) {
70
+ const runId: IteratorResult<number> = runIdGenerator.next()
71
+ let runMetadata: RunData | undefined = undefined
72
+ for (const result of run.results ?? []) {
73
+ runMetadata = {
74
+ id: runId.value,
75
+ run,
76
+ toolName: findToolComponent(run, result).name
77
+ }
78
+ model.findings.push(createFinding({ sarifPath, result, runMetadata }))
79
+ }
80
+ runMetadata ??= {
81
+ id: runId.value, run, toolName: findToolComponentDriver(run).name
82
+ }
83
+ model.runs.push(runMetadata)
84
+ }
85
+ }
86
+ return model
87
+ }
88
+
89
+ /**
90
+ * The main function to initialize a list of {@link SlackMessage} objects based
91
+ * on the given SARIF file(s).
92
+ * @param sarifModel An instance of {@link SarifModel} object.
93
+ * @param opts An instance of {@link SarifToSlackClientOptions} object.
94
+ * @returns A map where key is the SARIF file and value is an instance of
95
+ * {@link SlackMessage} object
96
+ * @private
97
+ */
98
+ private static async initialize(
99
+ sarifModel: SarifModel,
100
+ opts: Omit<SarifToSlackClientOptions, 'sarif' | 'log' | 'sendIf'>
101
+ ): Promise<SlackMessage> {
102
+ const message: SlackMessage = new SlackMessageBuilder(opts.webhookUrl, {
103
+ username: opts.username,
104
+ iconUrl: opts.iconUrl,
105
+ color: identifyColor(sarifModel.findings, opts.color),
106
+ representation: createRepresentation(sarifModel, opts.representation),
107
+ })
108
+ if (opts.header?.include) {
109
+ message.withHeader(opts.header?.value)
110
+ }
111
+ if (opts.footer?.include) {
112
+ message.withFooter(opts.footer?.value, opts.footer?.type)
113
+ }
114
+ if (opts.actor?.include) {
115
+ message.withActor(opts.actor?.value)
116
+ }
117
+ if (opts.run?.include) {
118
+ message.withRun()
119
+ }
120
+ return message
121
+ }
122
+
123
+ /**
124
+ * Sends a Slack message.
125
+ * @returns A promise that resolves when the message has been sent.
126
+ * @throws Error if a Slack message was not prepared for the given SARIF path.
127
+ * @public
128
+ */
129
+ public async send(): Promise<void> {
130
+ if (this._sarifModel == null) {
131
+ throw new Error('Could not parse SARIF file(s).')
132
+ }
133
+ if (this.shouldSendMessage) {
134
+ if (this._message == null) {
135
+ throw new Error('Slack message was not prepared.')
136
+ }
137
+ const text: string = await this._message.send()
138
+ Logger.info('Message sent. Status:', text)
139
+ } else {
140
+ Logger.info('Message was not sent based on the sendIf parameter:', SendIf[this._sendIf])
141
+ }
142
+ }
143
+
144
+ private get shouldSendMessage(): boolean {
145
+ if (this._sendIf == null) {
146
+ return true
147
+ }
148
+
149
+ switch (this._sendIf) {
150
+ case SendIf.SeverityCritical:
151
+ return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.Critical) != null
152
+ case SendIf.SeverityHigh:
153
+ return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.High) != null
154
+ case SendIf.SeverityHighOrHigher:
155
+ return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.High)
156
+ case SendIf.SeverityMedium:
157
+ return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.Medium) != null
158
+ case SendIf.SeverityMediumOrHigher:
159
+ return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.Medium)
160
+ case SendIf.SeverityLow:
161
+ return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.Low) != null
162
+ case SendIf.SeverityLowOrHigher:
163
+ return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.Low)
164
+ case SendIf.SeverityNone:
165
+ return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.None) != null
166
+ case SendIf.SeverityNoneOrHigher:
167
+ return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.None)
168
+ case SendIf.SeverityUnknown:
169
+ return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.Unknown) != null
170
+ case SendIf.SeverityUnknownOrHigher:
171
+ return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.Unknown)
172
+ case SendIf.LevelError:
173
+ return this._sarifModel?.findings.findByProperty('level', SecurityLevel.Error) != null
174
+ case SendIf.LevelWarning:
175
+ return this._sarifModel?.findings.findByProperty('level', SecurityLevel.Warning) != null
176
+ case SendIf.LevelWarningOrHigher:
177
+ return !!this._sarifModel?.findings.hasLevelOrHigher(SecurityLevel.Warning)
178
+ case SendIf.LevelNote:
179
+ return this._sarifModel?.findings.findByProperty('level', SecurityLevel.Note) != null
180
+ case SendIf.LevelNoteOrHigher:
181
+ return !!this._sarifModel?.findings.hasLevelOrHigher(SecurityLevel.Note)
182
+ case SendIf.LevelNone:
183
+ return this._sarifModel?.findings.findByProperty('level', SecurityLevel.None) != null
184
+ case SendIf.LevelNoneOrHigher:
185
+ return !!this._sarifModel?.findings.hasLevelOrHigher(SecurityLevel.None)
186
+ case SendIf.LevelUnknown:
187
+ return this._sarifModel?.findings.findByProperty('level', SecurityLevel.Unknown) != null
188
+ case SendIf.LevelUnknownOrHigher:
189
+ return !!this._sarifModel?.findings.hasLevelOrHigher(SecurityLevel.Unknown)
190
+ case SendIf.Always:
191
+ return true
192
+ case SendIf.Some:
193
+ return (this._sarifModel?.findings.length ?? 0) > 0
194
+ case SendIf.Empty:
195
+ return (this._sarifModel?.findings.length ?? 0) === 0
196
+ case SendIf.Never:
197
+ return false
198
+ default:
199
+ throw new Error(`Unknown sendIf parameter: ${this._sendIf}`)
200
+ }
201
+ }
202
+ }
@@ -2,21 +2,9 @@ import { AnyBlock } from '@slack/types'
2
2
  import { ContextBlock, HeaderBlock } from '@slack/types/dist/block-kit/blocks'
3
3
  import { TextObject } from '@slack/types/dist/block-kit/composition-objects'
4
4
  import { IncomingWebhook } from '@slack/webhook'
5
- import { Map as ImmutableMap } from 'immutable'
6
- import {
7
- CalculateResultsBy,
8
- FooterType,
9
- GroupResultsBy,
10
- SarifLog,
11
- SarifToSlackOutput,
12
- SlackMessage
13
- } from './types'
14
- import { LIB_VERSION } from './metadata'
15
- import {
16
- DataGroupedByRun,
17
- SarifModelPerSarif
18
- } from './model/SarifModelPerSarif';
19
- import { SecurityLevel, SecuritySeverity } from './model/types';
5
+ import { FooterType, SlackMessage } from './types'
6
+ import { version } from './metadata.json'
7
+ import Representation from './representations/Representation'
20
8
 
21
9
  /**
22
10
  * Options for the SlackMessageBuilder.
@@ -26,8 +14,7 @@ export type SlackMessageBuilderOptions = {
26
14
  username?: string
27
15
  iconUrl?: string
28
16
  color?: string
29
- sarif: SarifLog,
30
- output?: SarifToSlackOutput,
17
+ representation: Representation,
31
18
  }
32
19
 
33
20
  /**
@@ -35,36 +22,28 @@ export type SlackMessageBuilderOptions = {
35
22
  * @internal
36
23
  */
37
24
  export class SlackMessageBuilder implements SlackMessage {
38
- private readonly webhook: IncomingWebhook
39
- private readonly gitHubServerUrl: string
40
- private readonly color?: string
41
- private readonly sarifModelPerSarif: SarifModelPerSarif
42
- private readonly output: SarifToSlackOutput
43
- private header?: HeaderBlock
25
+ private readonly _webhook: IncomingWebhook
26
+ private readonly _gitHubServerUrl: string
27
+ private readonly _color?: string
28
+ private readonly _representation: Representation
44
29
 
45
- private footer?: ContextBlock
46
- private actor?: string
47
- private runId?: string
48
-
49
- public readonly sarif: SarifLog
30
+ private _header?: HeaderBlock
31
+ private _footer?: ContextBlock
32
+ private _actor?: string
33
+ private _runId?: string
50
34
 
51
35
  constructor(url: string, opts: SlackMessageBuilderOptions) {
52
- this.webhook = new IncomingWebhook(url, {
36
+ this._webhook = new IncomingWebhook(url, {
53
37
  username: opts.username || 'SARIF results',
54
38
  icon_url: opts.iconUrl
55
39
  })
56
- this.gitHubServerUrl = process.env.GITHUB_SERVER_URL || 'https://github.com'
57
- this.color = opts.color
58
- this.sarif = opts.sarif
59
- this.sarifModelPerSarif = new SarifModelPerSarif(opts.sarif)
60
- this.output = opts.output || {
61
- groupBy: GroupResultsBy.ToolName,
62
- calculateBy: CalculateResultsBy.Level
63
- }
40
+ this._gitHubServerUrl = process.env.GITHUB_SERVER_URL || 'https://github.com'
41
+ this._color = opts.color
42
+ this._representation = opts.representation
64
43
  }
65
44
 
66
45
  withHeader(header?: string): void {
67
- this.header = {
46
+ this._header = {
68
47
  type: 'header',
69
48
  text: {
70
49
  type: 'plain_text',
@@ -74,19 +53,19 @@ export class SlackMessageBuilder implements SlackMessage {
74
53
  }
75
54
 
76
55
  withActor(actor?: string): void {
77
- this.actor = actor || process.env.GITHUB_ACTOR
56
+ this._actor = actor || process.env.GITHUB_ACTOR
78
57
  }
79
58
 
80
59
  withRun(): void {
81
- this.runId = process.env.GITHUB_RUN_ID
60
+ this._runId = process.env.GITHUB_RUN_ID
82
61
  }
83
62
 
84
63
  withFooter(text?: string, type?: FooterType): void {
85
64
  const repoName = 'fabasoad/sarif-to-slack'
86
65
  const element: TextObject = text
87
66
  ? { type: type || FooterType.PlainText, text }
88
- : { type: FooterType.Markdown, text: `Generated by <${this.gitHubServerUrl}/${repoName}|@${repoName}@${LIB_VERSION}>` }
89
- this.footer = {
67
+ : { type: FooterType.Markdown, text: `Generated by <${this._gitHubServerUrl}/${repoName}|@${repoName}@${version}>` }
68
+ this._footer = {
90
69
  type: 'context',
91
70
  elements: [element],
92
71
  }
@@ -94,100 +73,41 @@ export class SlackMessageBuilder implements SlackMessage {
94
73
 
95
74
  async send(): Promise<string> {
96
75
  const blocks: AnyBlock[] = []
97
- if (this.header) {
98
- blocks.push(this.header)
76
+ if (this._header) {
77
+ blocks.push(this._header)
99
78
  }
100
79
  blocks.push({
101
80
  type: 'section',
102
81
  text: {
103
82
  type: 'mrkdwn',
104
- text: this.buildText()
83
+ text: this.buildText(),
105
84
  }
106
85
  })
107
- if (this.footer) {
108
- blocks.push(this.footer)
86
+ if (this._footer) {
87
+ blocks.push(this._footer)
109
88
  }
110
- const { text } = await this.webhook.send({
111
- attachments: [{ color: this.color, blocks }]
89
+ const { text } = await this._webhook.send({
90
+ attachments: [{ color: this._color, blocks }]
112
91
  })
113
92
  return text
114
93
  }
115
94
 
116
95
  private buildText(): string {
117
96
  const text: string[] = []
118
- if (this.actor) {
119
- const actorUrl = `${this.gitHubServerUrl}/${this.actor}`
120
- text.push(`_Triggered by <${actorUrl}|${this.actor}>_`)
97
+ if (this._actor) {
98
+ const actorUrl = `${this._gitHubServerUrl}/${this._actor}`
99
+ text.push(`_Triggered by <${actorUrl}|${this._actor}>_`)
121
100
  }
122
- text.push(this.composeSummary())
123
- if (this.runId) {
101
+ text.push(this._representation.compose())
102
+ if (this._runId) {
124
103
  let runText: string = 'Job '
125
104
  if (process.env.GITHUB_REPOSITORY) {
126
- runText += `<${this.gitHubServerUrl}/${process.env.GITHUB_REPOSITORY}/actions/runs/${this.runId}|#${this.runId}>`
105
+ runText += `<${this._gitHubServerUrl}/${process.env.GITHUB_REPOSITORY}/actions/runs/${this._runId}|#${this._runId}>`
127
106
  } else {
128
- runText += `#${this.runId}`
107
+ runText += `#${this._runId}`
129
108
  }
130
109
  text.push(runText)
131
110
  }
132
111
  return text.join('\n\n')
133
112
  }
134
-
135
- private composeSummaryWith(
136
- map: ImmutableMap<SecurityLevel | SecuritySeverity, number>,
137
- resultProcessor: (result: string) => string = (result: string): string => result,
138
- ): string {
139
- const stats = new Array<string>()
140
- for (const [key, count] of map.entries()) {
141
- stats.push(`*${key}*: ${count}`)
142
- }
143
- return resultProcessor(
144
- stats.length == 0 ? 'No issues found' : stats.join(', ')
145
- )
146
- }
147
-
148
- private composeSummary(): string {
149
- const summaries = new Array<string>()
150
- switch (this.output.groupBy) {
151
- case GroupResultsBy.ToolName: {
152
- const dataGroupedByToolName: Map<string, ImmutableMap<SecurityLevel | SecuritySeverity, number>> =
153
- this.output.calculateBy === CalculateResultsBy.Level
154
- ? this.sarifModelPerSarif.groupByToolNameWithSecurityLevel()
155
- : this.sarifModelPerSarif.groupByToolNameWithSecuritySeverity()
156
- for (const [toolName, map] of dataGroupedByToolName.entries()) {
157
- summaries.push(this.composeSummaryWith(
158
- map,
159
- (result: string): string => `*${toolName}*\n${result}`
160
- ))
161
- }
162
- break
163
- }
164
- case GroupResultsBy.Run: {
165
- const dataGroupedByRun: Array<DataGroupedByRun<SecurityLevel | SecuritySeverity>> =
166
- this.output.calculateBy === CalculateResultsBy.Level
167
- ? this.sarifModelPerSarif.groupByRunWithSecurityLevel()
168
- : this.sarifModelPerSarif.groupByRunWithSecuritySeverity()
169
- for (let i = 0; i < dataGroupedByRun.length; i++) {
170
- const { data, toolName } = dataGroupedByRun[i]
171
- summaries.push(this.composeSummaryWith(
172
- data,
173
- (result: string): string => `_[Run ${i + 1}]_: *${toolName}*\n${result}`
174
- ))
175
- }
176
- break
177
- }
178
- default: {
179
- const dataTotal: ImmutableMap<SecurityLevel | SecuritySeverity, number> =
180
- this.output.calculateBy === CalculateResultsBy.Level
181
- ? this.sarifModelPerSarif.groupByTotalWithSecurityLevel()
182
- : this.sarifModelPerSarif.groupByTotalWithSecuritySeverity()
183
- const toolNames: Set<string> = this.sarifModelPerSarif.listToolNames()
184
- summaries.push(this.composeSummaryWith(
185
- dataTotal,
186
- (result: string): string => `*${Array.from(toolNames).join('*, *')}*\n${result}`
187
- ))
188
- break
189
- }
190
- }
191
- return summaries.join('\n\n')
192
- }
193
113
  }
package/src/System.ts CHANGED
@@ -1,9 +1,16 @@
1
- import { LIB_VERSION } from './metadata'
1
+ import { version, sha, buildAt } from './metadata.json'
2
2
  import Logger from './Logger'
3
3
 
4
+ /**
5
+ * This class prints metadata information into the logs, such as library version,
6
+ * SHA and build time.
7
+ * @internal
8
+ */
4
9
  export default class System {
5
10
 
6
11
  public static initialize(): void {
7
- Logger.info(`Version: ${LIB_VERSION}`)
12
+ Logger.info(`@fabasoad/sarif-to-slack version: ${version}`)
13
+ Logger.info(`@fabasoad/sarif-to-slack sha: ${sha}`)
14
+ Logger.info(`@fabasoad/sarif-to-slack built at: ${buildAt}`)
8
15
  }
9
16
  }
package/src/index.ts CHANGED
@@ -4,24 +4,45 @@
4
4
  * Sarif to Slack message converter library.
5
5
  *
6
6
  * @remarks
7
- * This library provides a service to send a Slack messages based on the provided
7
+ * This library provides a client to send a Slack messages based on the provided
8
8
  * SARIF (Static Analysis Results Interchange Format) files.
9
9
  *
10
10
  * @example
11
11
  * ```typescript
12
- * import { SarifToSlackService, FooterType } from '@fabasoad/sarif-to-slack';
12
+ * import {
13
+ * Color,
14
+ * FooterType,
15
+ * LogLevel,
16
+ * RepresentationType,
17
+ * SarifToSlackClient,
18
+ * SendIf
19
+ * } from '@fabasoad/sarif-to-slack';
13
20
  *
14
- * const service = await SarifToSlackService.create({
21
+ * const client: SarifToSlackClient = await SarifToSlackClient.create({
15
22
  * webhookUrl: 'https://hooks.slack.com/services/your/webhook/url',
16
- * sarifPath: 'path/to/your/sarif/file.sarif',
23
+ * username: 'SARIF to Slack Bot',
24
+ * iconUrl: 'https://example.com/icon.png',
25
+ * color: {
26
+ * bySeverity: {
27
+ * critical: new Color('#ff0000'),
28
+ * high: new Color('#ff4500'),
29
+ * medium: new Color('#ffa500'),
30
+ * low: new Color('#ffff00'),
31
+ * none: new Color('#808080'),
32
+ * unknown: new Color('#800080'),
33
+ * empty: new Color('#d3d3d3'),
34
+ * },
35
+ * },
36
+ * sarif: {
37
+ * path: 'path/to/your/sarif-files',
38
+ * recursive: true,
39
+ * extension: 'sarif',
40
+ * },
17
41
  * log: {
18
42
  * level: LogLevel.Info,
19
43
  * template: '[{{logLevelName}}] [{{name}}] {{dateIsoStr}} ',
20
44
  * colored: false,
21
45
  * },
22
- * username: 'SARIF Bot',
23
- * iconUrl: 'https://example.com/icon.png',
24
- * color: '#36a64f',
25
46
  * header: {
26
47
  * include: true,
27
48
  * value: 'SARIF Analysis Results'
@@ -38,28 +59,34 @@
38
59
  * run: {
39
60
  * include: true
40
61
  * },
62
+ * representation: RepresentationType.CompactGroupByToolNamePerSeverity,
63
+ * sendIf: SendIf.MediumOrHigher,
41
64
  * });
42
- * await service.sendAll();
65
+ * await client.send();
43
66
  * ```
44
67
  *
45
- * @see {@link SarifToSlackService}
68
+ * @see {@link SarifToSlackClient}
46
69
  *
47
70
  * @packageDocumentation
48
71
  */
49
- export { SarifToSlackService } from './SarifToSlackService'
50
72
  export {
51
- CalculateResultsBy,
52
- FooterType,
53
- GroupResultsBy,
54
- LogLevel,
55
- SlackMessage
56
- } from './types'
57
- export type {
73
+ Color,
74
+ ColorOptions,
75
+ ColorGroupByLevel,
76
+ ColorGroupBySeverity
77
+ } from './model/Color'
78
+ export { SarifToSlackClient } from './SarifToSlackClient'
79
+ export {
58
80
  FooterOptions,
81
+ FooterType,
59
82
  IncludeAwareOptions,
60
83
  IncludeAwareWithValueOptions,
84
+ LogLevel,
61
85
  LogOptions,
62
- SarifLog,
63
- SarifToSlackOutput,
64
- SarifToSlackServiceOptions
86
+ RepresentationType,
87
+ SarifFileExtension,
88
+ SarifOptions,
89
+ SarifToSlackClientOptions,
90
+ SendIf,
91
+ SlackMessage,
65
92
  } from './types'