@fabasoad/sarif-to-slack 0.2.2 → 0.2.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.
@@ -76,7 +76,7 @@
76
76
  *
77
77
  * DEFAULT VALUE: "crlf"
78
78
  */
79
- // "newlineKind": "crlf",
79
+ "newlineKind": "lf",
80
80
 
81
81
  /**
82
82
  * Specifies how API Extractor sorts members of an enum when generating the .api.json file. By default, the output
@@ -85,7 +85,7 @@
85
85
  *
86
86
  * DEFAULT VALUE: "by-name"
87
87
  */
88
- // "enumMemberOrder": "by-name",
88
+ "enumMemberOrder": "preserve",
89
89
 
90
90
  /**
91
91
  * Set to true when invoking API Extractor's test harness. When `testMode` is true, the `toolVersion` field in the
package/dist/index.cjs ADDED
@@ -0,0 +1,629 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ CalculateResultsBy: () => CalculateResultsBy,
34
+ FooterType: () => FooterType,
35
+ GroupResultsBy: () => GroupResultsBy,
36
+ LogLevel: () => LogLevel,
37
+ SarifToSlackService: () => SarifToSlackService
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/SarifToSlackService.ts
42
+ var import_fs = require("fs");
43
+
44
+ // src/Logger.ts
45
+ var import_tslog = require("tslog");
46
+
47
+ // src/types.ts
48
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
49
+ LogLevel2[LogLevel2["Silly"] = 0] = "Silly";
50
+ LogLevel2[LogLevel2["Trace"] = 1] = "Trace";
51
+ LogLevel2[LogLevel2["Debug"] = 2] = "Debug";
52
+ LogLevel2[LogLevel2["Info"] = 3] = "Info";
53
+ LogLevel2[LogLevel2["Warning"] = 4] = "Warning";
54
+ LogLevel2[LogLevel2["Error"] = 5] = "Error";
55
+ LogLevel2[LogLevel2["Fatal"] = 6] = "Fatal";
56
+ return LogLevel2;
57
+ })(LogLevel || {});
58
+ var FooterType = /* @__PURE__ */ ((FooterType2) => {
59
+ FooterType2["PlainText"] = "plain_text";
60
+ FooterType2["Markdown"] = "mrkdwn";
61
+ return FooterType2;
62
+ })(FooterType || {});
63
+ var GroupResultsBy = /* @__PURE__ */ ((GroupResultsBy2) => {
64
+ GroupResultsBy2[GroupResultsBy2["ToolName"] = 0] = "ToolName";
65
+ GroupResultsBy2[GroupResultsBy2["Run"] = 1] = "Run";
66
+ GroupResultsBy2[GroupResultsBy2["Total"] = 2] = "Total";
67
+ return GroupResultsBy2;
68
+ })(GroupResultsBy || {});
69
+ var CalculateResultsBy = /* @__PURE__ */ ((CalculateResultsBy2) => {
70
+ CalculateResultsBy2[CalculateResultsBy2["Level"] = 0] = "Level";
71
+ CalculateResultsBy2[CalculateResultsBy2["Severity"] = 1] = "Severity";
72
+ return CalculateResultsBy2;
73
+ })(CalculateResultsBy || {});
74
+
75
+ // src/Logger.ts
76
+ var Logger = class _Logger {
77
+ static DEFAULT_LOG_LEVEL = 3 /* Info */;
78
+ static DEFAULT_LOG_TEMPLATE = "[{{logLevelName}}] [{{name}}] {{dateIsoStr}} ";
79
+ static DEFAULT_LOG_COLORED = true;
80
+ static instance;
81
+ static initialize(opts) {
82
+ if (!_Logger.instance) {
83
+ _Logger.instance = new import_tslog.Logger({
84
+ name: "@fabasoad/sarif-to-slack",
85
+ minLevel: process.env.ACTIONS_STEP_DEBUG === "true" ? 0 /* Silly */ : opts?.level ?? _Logger.DEFAULT_LOG_LEVEL,
86
+ type: "pretty",
87
+ prettyLogTimeZone: "UTC",
88
+ prettyLogTemplate: opts?.template ?? _Logger.DEFAULT_LOG_TEMPLATE,
89
+ stylePrettyLogs: opts?.colored ?? _Logger.DEFAULT_LOG_COLORED
90
+ });
91
+ }
92
+ }
93
+ static warn(...args) {
94
+ _Logger.instance.warn(...args);
95
+ }
96
+ static info(...args) {
97
+ _Logger.instance.info(...args);
98
+ }
99
+ static debug(...args) {
100
+ _Logger.instance.debug(...args);
101
+ }
102
+ };
103
+
104
+ // src/Processors.ts
105
+ var fs = __toESM(require("fs"));
106
+ var path = __toESM(require("path"));
107
+ function processColor(color) {
108
+ switch (color) {
109
+ case "success":
110
+ Logger.info(`Converting "${color}" to #008000`);
111
+ return "#008000";
112
+ case "failure":
113
+ Logger.info(`Converting "${color}" to #ff0000`);
114
+ return "#ff0000";
115
+ case "cancelled":
116
+ Logger.info(`Converting "${color}" to #0047ab`);
117
+ return "#0047ab";
118
+ case "skipped":
119
+ Logger.info(`Converting "${color}" to #808080`);
120
+ return "#808080";
121
+ default:
122
+ Logger.debug(`"${color}" color is not a CI status identifier. Returning as is.`);
123
+ return color;
124
+ }
125
+ }
126
+ function processSarifPath(sarifPath) {
127
+ if (!fs.existsSync(sarifPath)) {
128
+ throw new Error(`"sarif-path" does not exist: ${sarifPath}`);
129
+ }
130
+ const sarifStats = fs.statSync(sarifPath);
131
+ if (sarifStats.isDirectory()) {
132
+ Logger.info(`"sarif-path" is a directory: ${sarifPath}`);
133
+ const files = fs.readdirSync(sarifPath);
134
+ const filteredFiles = files.filter(
135
+ (file) => path.extname(file).toLowerCase() === ".sarif"
136
+ );
137
+ Logger.info(`Found ${filteredFiles.length} SARIF files in ${sarifPath} directory`);
138
+ Logger.debug(`Filtered SARIF files: ${filteredFiles.join(", ")}`);
139
+ return filteredFiles.map((file) => path.join(sarifPath, file));
140
+ }
141
+ if (sarifStats.isFile()) {
142
+ Logger.info(`"sarif-path" is a file: ${sarifPath}`);
143
+ return [sarifPath];
144
+ }
145
+ throw new Error(`"sarif-path" is neither a file nor a directory: ${sarifPath}`);
146
+ }
147
+
148
+ // src/SlackMessageBuilder.ts
149
+ var import_webhook = require("@slack/webhook");
150
+
151
+ // src/version.ts
152
+ var LIB_VERSION = "0.2.3";
153
+
154
+ // src/model/SarifModelPerSarif.ts
155
+ var import_immutable2 = require("immutable");
156
+
157
+ // src/utils/SarifUtils.ts
158
+ function findRuleByResult(run, result) {
159
+ const ruleData = {};
160
+ if (result.rule) {
161
+ if (result.rule?.index) {
162
+ ruleData.index = result.rule.index;
163
+ }
164
+ if (result.rule?.id) {
165
+ ruleData.id = result.rule.id;
166
+ }
167
+ }
168
+ if (!ruleData.index && result.ruleIndex) {
169
+ ruleData.index = result.ruleIndex;
170
+ }
171
+ if (ruleData.index && run.tool.driver?.rules && ruleData.index < run.tool.driver.rules.length) {
172
+ return run.tool.driver.rules[ruleData.index];
173
+ }
174
+ if (result.ruleId && run.tool.driver?.rules) {
175
+ return run.tool.driver.rules.find(
176
+ (r) => r.id === result.ruleId
177
+ );
178
+ }
179
+ return void 0;
180
+ }
181
+ function tryGetRulePropertyByResult(run, result, propertyName) {
182
+ const rule = findRuleByResult(run, result);
183
+ if (rule && rule.properties && propertyName in rule.properties) {
184
+ return rule.properties[propertyName];
185
+ }
186
+ return void 0;
187
+ }
188
+
189
+ // src/model/types.ts
190
+ var SecuritySeverityOrder = [
191
+ "Critical" /* Critical */,
192
+ "High" /* High */,
193
+ "Medium" /* Medium */,
194
+ "Low" /* Low */,
195
+ "None" /* None */,
196
+ "Unknown" /* Unknown */
197
+ ];
198
+ var SecurityLevelOrder = [
199
+ "Error" /* Error */,
200
+ "Warning" /* Warning */,
201
+ "Note" /* Note */,
202
+ "Unknown" /* Unknown */
203
+ ];
204
+
205
+ // src/model/SarifModelPerRun.ts
206
+ var import_immutable = require("immutable");
207
+
208
+ // src/utils/SortUtils.ts
209
+ function sortSecurityLevelMap(map) {
210
+ return map.sortBy(
211
+ (_, level) => level,
212
+ (a, b) => SecurityLevelOrder.indexOf(a) - SecurityLevelOrder.indexOf(b)
213
+ ).asImmutable();
214
+ }
215
+ function sortSecuritySeverityMap(map) {
216
+ return map.sortBy(
217
+ (_, severity) => severity,
218
+ (a, b) => SecuritySeverityOrder.indexOf(a) - SecuritySeverityOrder.indexOf(b)
219
+ ).asImmutable();
220
+ }
221
+
222
+ // src/model/SarifModelPerRun.ts
223
+ var SarifModelPerRun = class {
224
+ toolName;
225
+ _securitySeverityMap;
226
+ _securityLevelMap;
227
+ constructor(run) {
228
+ this.toolName = run.tool.driver.name;
229
+ this._securitySeverityMap = (0, import_immutable.Map)().asMutable();
230
+ this._securityLevelMap = (0, import_immutable.Map)().asMutable();
231
+ this.buildSecuritySeverityMap(run);
232
+ this.buildSecurityLevelMap(run);
233
+ }
234
+ identifySecuritySeverity(score) {
235
+ if (score === void 0) {
236
+ return "Unknown" /* Unknown */;
237
+ }
238
+ if (score >= 9 && score <= 10) {
239
+ return "Critical" /* Critical */;
240
+ }
241
+ if (score >= 7) {
242
+ return "High" /* High */;
243
+ }
244
+ if (score >= 4) {
245
+ return "Medium" /* Medium */;
246
+ }
247
+ if (score >= 0.1) {
248
+ return "Low" /* Low */;
249
+ }
250
+ if (score == 0) {
251
+ return "None" /* None */;
252
+ }
253
+ Logger.warn(`Unsupported "${score}" security severity. Saving as "Unknown".`);
254
+ return "Unknown" /* Unknown */;
255
+ }
256
+ identifySecurityLevel(level) {
257
+ if (level === void 0) {
258
+ return "Unknown" /* Unknown */;
259
+ }
260
+ if (level.toLowerCase() === "error") {
261
+ return "Error" /* Error */;
262
+ }
263
+ if (level.toLowerCase() === "warning") {
264
+ return "Warning" /* Warning */;
265
+ }
266
+ if (level.toLowerCase() === "note") {
267
+ return "Note" /* Note */;
268
+ }
269
+ Logger.warn(`Unsupported ${level} security level. Saving as "Unknown".`);
270
+ return "Unknown" /* Unknown */;
271
+ }
272
+ buildSecuritySeverityMap(run) {
273
+ const results = run.results ?? [];
274
+ for (const result of results) {
275
+ const severity = this.identifySecuritySeverity(
276
+ tryGetRulePropertyByResult(run, result, "security-severity")
277
+ );
278
+ const count = this._securitySeverityMap.get(severity) || 0;
279
+ this._securitySeverityMap.set(severity, count + 1);
280
+ }
281
+ }
282
+ tryGetSecurityLevel(run, result) {
283
+ if (result.level) {
284
+ return result.level;
285
+ }
286
+ return tryGetRulePropertyByResult(run, result, "problem.severity");
287
+ }
288
+ buildSecurityLevelMap(run) {
289
+ const results = run.results ?? [];
290
+ for (const result of results) {
291
+ const level = this.identifySecurityLevel(
292
+ this.tryGetSecurityLevel(run, result)
293
+ );
294
+ const count = this._securityLevelMap.get(level) || 0;
295
+ this._securityLevelMap.set(level, count + 1);
296
+ }
297
+ }
298
+ get securitySeverityMap() {
299
+ return sortSecuritySeverityMap(this._securitySeverityMap);
300
+ }
301
+ get securityLevelMap() {
302
+ return sortSecurityLevelMap(this._securityLevelMap);
303
+ }
304
+ };
305
+
306
+ // src/model/SarifModelPerSarif.ts
307
+ var SarifModelPerSarif = class {
308
+ sarifModelPerRunList;
309
+ constructor(sarif) {
310
+ this.sarifModelPerRunList = new Array();
311
+ this.buildRunsList(sarif);
312
+ }
313
+ buildRunsList(sarif) {
314
+ for (const run of sarif.runs) {
315
+ this.sarifModelPerRunList.push(new SarifModelPerRun(run));
316
+ }
317
+ }
318
+ groupByToolNameWithSecurityLevel() {
319
+ const result = /* @__PURE__ */ new Map();
320
+ for (const sarifModelPerRun of this.sarifModelPerRunList) {
321
+ if (!result.has(sarifModelPerRun.toolName)) {
322
+ result.set(sarifModelPerRun.toolName, (0, import_immutable2.Map)().asMutable());
323
+ }
324
+ for (const [k, v] of sarifModelPerRun.securityLevelMap.entries()) {
325
+ const count = result.get(sarifModelPerRun.toolName)?.get(k) || 0;
326
+ result.get(sarifModelPerRun.toolName)?.set(k, count + v);
327
+ }
328
+ }
329
+ for (const [k, v] of result) {
330
+ result.set(k, sortSecurityLevelMap(v));
331
+ }
332
+ return result;
333
+ }
334
+ groupByRunWithSecurityLevel() {
335
+ const result = new Array();
336
+ for (const sarifModelPerRun of this.sarifModelPerRunList) {
337
+ result.push({
338
+ toolName: sarifModelPerRun.toolName,
339
+ data: sarifModelPerRun.securityLevelMap
340
+ });
341
+ }
342
+ return result;
343
+ }
344
+ groupByTotalWithSecurityLevel() {
345
+ const result = (0, import_immutable2.Map)().asMutable();
346
+ for (const sarifModelPerRun of this.sarifModelPerRunList) {
347
+ for (const [k, v] of sarifModelPerRun.securityLevelMap.entries()) {
348
+ const count = result.get(k) || 0;
349
+ result.set(k, count + v);
350
+ }
351
+ }
352
+ return sortSecurityLevelMap(result);
353
+ }
354
+ groupByToolNameWithSecuritySeverity() {
355
+ const result = /* @__PURE__ */ new Map();
356
+ for (const sarifModelPerRun of this.sarifModelPerRunList) {
357
+ if (!result.has(sarifModelPerRun.toolName)) {
358
+ result.set(sarifModelPerRun.toolName, (0, import_immutable2.Map)().asMutable());
359
+ }
360
+ for (const [k, v] of sarifModelPerRun.securitySeverityMap.entries()) {
361
+ const count = result.get(sarifModelPerRun.toolName)?.get(k) || 0;
362
+ result.get(sarifModelPerRun.toolName)?.set(k, count + v);
363
+ }
364
+ }
365
+ for (const [k, v] of result.entries()) {
366
+ result.set(k, sortSecuritySeverityMap(v));
367
+ }
368
+ return result;
369
+ }
370
+ groupByRunWithSecuritySeverity() {
371
+ const result = new Array();
372
+ for (const sarifModelPerRun of this.sarifModelPerRunList) {
373
+ result.push({
374
+ toolName: sarifModelPerRun.toolName,
375
+ data: sarifModelPerRun.securitySeverityMap
376
+ });
377
+ }
378
+ return result;
379
+ }
380
+ groupByTotalWithSecuritySeverity() {
381
+ const result = (0, import_immutable2.Map)().asMutable();
382
+ for (const sarifModelPerRun of this.sarifModelPerRunList) {
383
+ for (const [k, v] of sarifModelPerRun.securitySeverityMap.entries()) {
384
+ const count = result.get(k) || 0;
385
+ result.set(k, count + v);
386
+ }
387
+ }
388
+ return sortSecuritySeverityMap(result);
389
+ }
390
+ listToolNames() {
391
+ const toolNames = /* @__PURE__ */ new Set();
392
+ for (const sarifModelPerRun of this.sarifModelPerRunList) {
393
+ toolNames.add(sarifModelPerRun.toolName);
394
+ }
395
+ return toolNames;
396
+ }
397
+ };
398
+
399
+ // src/SlackMessageBuilder.ts
400
+ var SlackMessageBuilder = class {
401
+ webhook;
402
+ gitHubServerUrl;
403
+ color;
404
+ sarifModelPerSarif;
405
+ output;
406
+ header;
407
+ footer;
408
+ actor;
409
+ runId;
410
+ sarif;
411
+ constructor(url, opts) {
412
+ this.webhook = new import_webhook.IncomingWebhook(url, {
413
+ username: opts.username || "SARIF results",
414
+ icon_url: opts.iconUrl
415
+ });
416
+ this.gitHubServerUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
417
+ this.color = opts.color;
418
+ this.sarif = opts.sarif;
419
+ this.sarifModelPerSarif = new SarifModelPerSarif(opts.sarif);
420
+ this.output = opts.output || {
421
+ groupBy: 0 /* ToolName */,
422
+ calculateBy: 0 /* Level */
423
+ };
424
+ }
425
+ withHeader(header) {
426
+ this.header = {
427
+ type: "header",
428
+ text: {
429
+ type: "plain_text",
430
+ text: header || process.env.GITHUB_REPOSITORY || "SARIF results"
431
+ }
432
+ };
433
+ }
434
+ withActor(actor) {
435
+ this.actor = actor || process.env.GITHUB_ACTOR;
436
+ }
437
+ withRun() {
438
+ this.runId = process.env.GITHUB_RUN_ID;
439
+ }
440
+ withFooter(text, type) {
441
+ const repoName = "fabasoad/sarif-to-slack";
442
+ const element = text ? { type: type || "plain_text" /* PlainText */, text } : { type: "mrkdwn" /* Markdown */, text: `Generated by <${this.gitHubServerUrl}/${repoName}|@${repoName}@${LIB_VERSION}>` };
443
+ this.footer = {
444
+ type: "context",
445
+ elements: [element]
446
+ };
447
+ }
448
+ async send() {
449
+ const blocks = [];
450
+ if (this.header) {
451
+ blocks.push(this.header);
452
+ }
453
+ blocks.push({
454
+ type: "section",
455
+ text: {
456
+ type: "mrkdwn",
457
+ text: this.buildText()
458
+ }
459
+ });
460
+ if (this.footer) {
461
+ blocks.push(this.footer);
462
+ }
463
+ const { text } = await this.webhook.send({
464
+ attachments: [{ color: this.color, blocks }]
465
+ });
466
+ return text;
467
+ }
468
+ buildText() {
469
+ const text = [];
470
+ if (this.actor) {
471
+ const actorUrl = `${this.gitHubServerUrl}/${this.actor}`;
472
+ text.push(`_Triggered by <${actorUrl}|${this.actor}>_`);
473
+ }
474
+ text.push(this.composeSummary());
475
+ if (this.runId) {
476
+ let runText = "Job ";
477
+ if (process.env.GITHUB_REPOSITORY) {
478
+ runText += `<${this.gitHubServerUrl}/${process.env.GITHUB_REPOSITORY}/actions/runs/${this.runId}|#${this.runId}>`;
479
+ } else {
480
+ runText += `#${this.runId}`;
481
+ }
482
+ text.push(runText);
483
+ }
484
+ return text.join("\n\n");
485
+ }
486
+ composeSummaryWith(map, resultProcessor = (result) => result) {
487
+ const stats = new Array();
488
+ for (const [key, count] of map.entries()) {
489
+ stats.push(`*${key}*: ${count}`);
490
+ }
491
+ return resultProcessor(
492
+ stats.length == 0 ? "No issues found" : stats.join(", ")
493
+ );
494
+ }
495
+ composeSummary() {
496
+ const summaries = new Array();
497
+ switch (this.output.groupBy) {
498
+ case 0 /* ToolName */: {
499
+ const dataGroupedByToolName = this.output.calculateBy === 0 /* Level */ ? this.sarifModelPerSarif.groupByToolNameWithSecurityLevel() : this.sarifModelPerSarif.groupByToolNameWithSecuritySeverity();
500
+ for (const [toolName, map] of dataGroupedByToolName.entries()) {
501
+ summaries.push(this.composeSummaryWith(
502
+ map,
503
+ (result) => `*${toolName}*
504
+ ${result}`
505
+ ));
506
+ }
507
+ break;
508
+ }
509
+ case 1 /* Run */: {
510
+ const dataGroupedByRun = this.output.calculateBy === 0 /* Level */ ? this.sarifModelPerSarif.groupByRunWithSecurityLevel() : this.sarifModelPerSarif.groupByRunWithSecuritySeverity();
511
+ for (let i = 0; i < dataGroupedByRun.length; i++) {
512
+ const { data, toolName } = dataGroupedByRun[i];
513
+ summaries.push(this.composeSummaryWith(
514
+ data,
515
+ (result) => `_[Run ${i + 1}]_: *${toolName}*
516
+ ${result}`
517
+ ));
518
+ }
519
+ break;
520
+ }
521
+ default: {
522
+ const dataTotal = this.output.calculateBy === 0 /* Level */ ? this.sarifModelPerSarif.groupByTotalWithSecurityLevel() : this.sarifModelPerSarif.groupByTotalWithSecuritySeverity();
523
+ const toolNames = this.sarifModelPerSarif.listToolNames();
524
+ summaries.push(this.composeSummaryWith(
525
+ dataTotal,
526
+ (result) => `*${Array.from(toolNames).join("*, *")}*
527
+ ${result}`
528
+ ));
529
+ break;
530
+ }
531
+ }
532
+ return summaries.join("\n\n");
533
+ }
534
+ };
535
+
536
+ // src/SarifToSlackService.ts
537
+ async function initialize(opts) {
538
+ const slackMessages = /* @__PURE__ */ new Map();
539
+ const sarifFiles = processSarifPath(opts.sarifPath);
540
+ if (sarifFiles.length === 0) {
541
+ throw new Error(`No SARIF files found at the provided path: ${opts.sarifPath}`);
542
+ }
543
+ for (const sarifFile of sarifFiles) {
544
+ const jsonString = await import_fs.promises.readFile(sarifFile, "utf8");
545
+ const messageBuilder = new SlackMessageBuilder(opts.webhookUrl, {
546
+ username: opts.username,
547
+ iconUrl: opts.iconUrl,
548
+ color: processColor(opts.color),
549
+ sarif: JSON.parse(jsonString),
550
+ output: opts.output
551
+ });
552
+ if (opts.header?.include) {
553
+ messageBuilder.withHeader(opts.header?.value);
554
+ }
555
+ if (opts.footer?.include) {
556
+ messageBuilder.withFooter(opts.footer?.value, opts.footer?.type);
557
+ }
558
+ if (opts.actor?.include) {
559
+ messageBuilder.withActor(opts.actor?.value);
560
+ }
561
+ if (opts.run?.include) {
562
+ messageBuilder.withRun();
563
+ }
564
+ slackMessages.set(sarifFile, messageBuilder);
565
+ }
566
+ return slackMessages;
567
+ }
568
+ var SarifToSlackService = class _SarifToSlackService {
569
+ _slackMessages;
570
+ constructor() {
571
+ this._slackMessages = /* @__PURE__ */ new Map();
572
+ }
573
+ /**
574
+ * Gets the Slack messages prepared for each SARIF file.
575
+ * @returns A read-only map where keys are SARIF file paths and values are SlackMessage instances.
576
+ * @public
577
+ */
578
+ get slackMessages() {
579
+ return this._slackMessages;
580
+ }
581
+ /**
582
+ * Creates an instance of SarifToSlackService.
583
+ * @param opts - Options for the service, including webhook URL, SARIF path, and other configurations.
584
+ * @returns A promise that resolves to an instance of SarifToSlackService.
585
+ * @throws Error if no SARIF files are found at the provided path.
586
+ * @public
587
+ */
588
+ static async create(opts) {
589
+ Logger.initialize(opts.log);
590
+ const instance = new _SarifToSlackService();
591
+ const map = await initialize(opts);
592
+ map.forEach((val, key) => instance._slackMessages.set(key, val));
593
+ return instance;
594
+ }
595
+ /**
596
+ * Sends all prepared Slack messages.
597
+ * @returns A promise that resolves when all messages have been sent.
598
+ * @throws Error if a Slack message was not prepared for a SARIF path.
599
+ * @public
600
+ */
601
+ async sendAll() {
602
+ for (const sarifPath of this._slackMessages.keys()) {
603
+ await this.send(sarifPath);
604
+ }
605
+ }
606
+ /**
607
+ * Sends a Slack message for a specific SARIF path.
608
+ * @param sarifPath - The path of the SARIF file for which the message should be sent.
609
+ * @returns A promise that resolves when the message has been sent.
610
+ * @throws Error if a Slack message was not prepared for the given SARIF path.
611
+ * @public
612
+ */
613
+ async send(sarifPath) {
614
+ const message = this._slackMessages.get(sarifPath);
615
+ if (!message) {
616
+ throw new Error(`Slack message was not prepared for SARIF path: ${sarifPath}.`);
617
+ }
618
+ const text = await message.send();
619
+ Logger.info(`Message sent for ${sarifPath} file. Status:`, text);
620
+ }
621
+ };
622
+ // Annotate the CommonJS export names for ESM import in node:
623
+ 0 && (module.exports = {
624
+ CalculateResultsBy,
625
+ FooterType,
626
+ GroupResultsBy,
627
+ LogLevel,
628
+ SarifToSlackService
629
+ });
@@ -1,278 +1,278 @@
1
- /**
2
- * Sarif to Slack message converter library.
3
- *
4
- * @remarks
5
- * This library provides a service to send a Slack messages based on the provided
6
- * SARIF (Static Analysis Results Interchange Format) files.
7
- *
8
- * @example
9
- * ```typescript
10
- * import { SarifToSlackService, FooterType } from '@fabasoad/sarif-to-slack';
11
- *
12
- * const service = await SarifToSlackService.create({
13
- * webhookUrl: 'https://hooks.slack.com/services/your/webhook/url',
14
- * sarifPath: 'path/to/your/sarif/file.sarif',
15
- * log: {
16
- * level: LogLevel.Info,
17
- * template: '[{{logLevelName}}] [{{name}}] {{dateIsoStr}} ',
18
- * colored: false,
19
- * },
20
- * username: 'SARIF Bot',
21
- * iconUrl: 'https://example.com/icon.png',
22
- * color: '#36a64f',
23
- * header: {
24
- * include: true,
25
- * value: 'SARIF Analysis Results'
26
- * },
27
- * footer: {
28
- * include: true,
29
- * type: FooterType.PLAIN_TEXT,
30
- * value: 'Generated by @fabasoad/sarif-to-slack'
31
- * },
32
- * actor: {
33
- * include: true,
34
- * value: 'fabasoad'
35
- * },
36
- * run: {
37
- * include: true
38
- * },
39
- * });
40
- * await service.sendAll();
41
- * ```
42
- *
43
- * @see {@link SarifToSlackService}
44
- *
45
- * @packageDocumentation
46
- */
47
-
48
- import type { Log } from 'sarif';
49
-
50
- /**
51
- * Enum representing how to calculate results.
52
- * @public
53
- */
54
- export declare enum CalculateResultsBy {
55
- /**
56
- * Calculates results by the security level of the findings: Error, Warning,
57
- * Note and Unknown. At first, it tries to get the security level from runs[].results[].level
58
- * property. If it is not defined, it tries to get the security level from the
59
- * respective rule of each result, using the rules[].properties['problem.severity']
60
- * property.
61
- */
62
- Level = 0,
63
- /**
64
- * Calculates results by the security severity of the findings: Critical, High,
65
- * Medium, Low, None and Unknown. it tries to get the security severity from the
66
- * respective rule of each result, using the rules[].properties['security-severity']
67
- * property. This property contains CVSS score, which is then mapped to the
68
- * security severity value.
69
- */
70
- Severity = 1
71
- }
72
-
73
- /**
74
- * Options for the footer of a Slack message. "type" is ignored if "value" is
75
- * not defined.
76
- * @public
77
- */
78
- export declare type FooterOptions = IncludeAwareWithValueOptions & {
79
- type?: FooterType;
80
- };
81
-
82
- /**
83
- * Enum representing the type of footer in a Slack message.
84
- * @public
85
- */
86
- export declare enum FooterType {
87
- /**
88
- * Represents a plain text footer. Text is not formatted and appears as-is.
89
- */
90
- PlainText = "plain_text",
91
- /**
92
- * Represents a footer with Markdown formatting. Text can include formatting
93
- * such as bold, italics, and links.
94
- */
95
- Markdown = "mrkdwn"
96
- }
97
-
98
- /**
99
- * Enum representing how to group results.
100
- * @public
101
- */
102
- export declare enum GroupResultsBy {
103
- /**
104
- * Groups results by the tool name. Particularly, groups by the runs[].tool.driver.name
105
- * property from the SARIF file(s).
106
- */
107
- ToolName = 0,
108
- /**
109
- * Groups results by the run. It provides the result from each run individually.
110
- */
111
- Run = 1,
112
- /**
113
- * Does not group results. It provides the result from all the runs from all
114
- * the provided SARIF files.
115
- */
116
- Total = 2
117
- }
118
-
119
- /**
120
- * Type representing properties that indicate whether to include certain information
121
- * in the Slack message.
122
- * @public
123
- */
124
- export declare type IncludeAwareOptions = {
125
- include: boolean;
126
- };
127
-
128
- /**
129
- * Type representing properties that indicate whether to include certain information
130
- * in the Slack message, along with an optional value.
131
- * @public
132
- */
133
- export declare type IncludeAwareWithValueOptions = IncludeAwareOptions & {
134
- value?: string;
135
- };
136
-
137
- /**
138
- * Enum representing log levels for the service.
139
- * @public
140
- */
141
- export declare enum LogLevel {
142
- /**
143
- * Represents the most verbose logging level, typically used for detailed
144
- * debugging information.
145
- */
146
- Silly = 0,
147
- /**
148
- * Represents a logging level for tracing the flow of the application.
149
- */
150
- Trace = 1,
151
- /**
152
- * Represents a logging level for debugging information that is less verbose
153
- * than silly.
154
- */
155
- Debug = 2,
156
- /**
157
- * Represents a logging level for general informational messages that highlight
158
- * the progress of the application.
159
- */
160
- Info = 3,
161
- /**
162
- * Represents a logging level for potentially harmful situations that require
163
- * attention.
164
- */
165
- Warning = 4,
166
- /**
167
- * Represents a logging level for error conditions that do not require immediate
168
- * action but should be noted.
169
- */
170
- Error = 5,
171
- /**
172
- * Represents a logging level for critical errors that require immediate attention
173
- * and may cause the application to terminate.
174
- */
175
- Fatal = 6
176
- }
177
-
178
- /**
179
- * Options for logging.
180
- * @public
181
- */
182
- export declare type LogOptions = {
183
- level?: LogLevel;
184
- /**
185
- * More details here: https://github.com/fullstack-build/tslog?tab=readme-ov-file#pretty-templates-and-styles-color-settings
186
- */
187
- template?: string;
188
- colored?: boolean;
189
- };
190
-
191
- /**
192
- * Type representing a SARIF log.
193
- * @public
194
- */
195
- export declare type SarifLog = Log;
196
-
197
- /**
198
- * Options for how to output the results in the Slack message.
199
- * @public
200
- */
201
- export declare type SarifToSlackOutput = {
202
- groupBy?: GroupResultsBy;
203
- calculateBy?: CalculateResultsBy;
204
- };
205
-
206
- /**
207
- * Service to convert SARIF files to Slack messages and send them.
208
- * @public
209
- */
210
- export declare class SarifToSlackService {
211
- private readonly _slackMessages;
212
- private constructor();
213
- /**
214
- * Gets the Slack messages prepared for each SARIF file.
215
- * @returns A read-only map where keys are SARIF file paths and values are SlackMessage instances.
216
- * @public
217
- */
218
- get slackMessages(): ReadonlyMap<string, SlackMessage>;
219
- /**
220
- * Creates an instance of SarifToSlackService.
221
- * @param opts - Options for the service, including webhook URL, SARIF path, and other configurations.
222
- * @returns A promise that resolves to an instance of SarifToSlackService.
223
- * @throws Error if no SARIF files are found at the provided path.
224
- * @public
225
- */
226
- static create(opts: SarifToSlackServiceOptions): Promise<SarifToSlackService>;
227
- /**
228
- * Sends all prepared Slack messages.
229
- * @returns A promise that resolves when all messages have been sent.
230
- * @throws Error if a Slack message was not prepared for a SARIF path.
231
- * @public
232
- */
233
- sendAll(): Promise<void>;
234
- /**
235
- * Sends a Slack message for a specific SARIF path.
236
- * @param sarifPath - The path of the SARIF file for which the message should be sent.
237
- * @returns A promise that resolves when the message has been sent.
238
- * @throws Error if a Slack message was not prepared for the given SARIF path.
239
- * @public
240
- */
241
- send(sarifPath: string): Promise<void>;
242
- }
243
-
244
- /**
245
- * Options for the SarifToSlackService.
246
- * @public
247
- */
248
- export declare type SarifToSlackServiceOptions = {
249
- webhookUrl: string;
250
- sarifPath: string;
251
- username?: string;
252
- iconUrl?: string;
253
- color?: string;
254
- log?: LogOptions;
255
- header?: IncludeAwareWithValueOptions;
256
- footer?: FooterOptions;
257
- actor?: IncludeAwareWithValueOptions;
258
- run?: IncludeAwareOptions;
259
- output?: SarifToSlackOutput;
260
- };
261
-
262
- /**
263
- * Interface for a Slack message that can be sent.
264
- * @public
265
- */
266
- export declare interface SlackMessage {
267
- /**
268
- * Sends the Slack message.
269
- * @returns A promise that resolves to the response from the Slack webhook.
270
- */
271
- send: () => Promise<string>;
272
- /**
273
- * The SARIF log associated with this Slack message.
274
- */
275
- sarif: SarifLog;
276
- }
277
-
278
- export { }
1
+ /**
2
+ * Sarif to Slack message converter library.
3
+ *
4
+ * @remarks
5
+ * This library provides a service to send a Slack messages based on the provided
6
+ * SARIF (Static Analysis Results Interchange Format) files.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { SarifToSlackService, FooterType } from '@fabasoad/sarif-to-slack';
11
+ *
12
+ * const service = await SarifToSlackService.create({
13
+ * webhookUrl: 'https://hooks.slack.com/services/your/webhook/url',
14
+ * sarifPath: 'path/to/your/sarif/file.sarif',
15
+ * log: {
16
+ * level: LogLevel.Info,
17
+ * template: '[{{logLevelName}}] [{{name}}] {{dateIsoStr}} ',
18
+ * colored: false,
19
+ * },
20
+ * username: 'SARIF Bot',
21
+ * iconUrl: 'https://example.com/icon.png',
22
+ * color: '#36a64f',
23
+ * header: {
24
+ * include: true,
25
+ * value: 'SARIF Analysis Results'
26
+ * },
27
+ * footer: {
28
+ * include: true,
29
+ * type: FooterType.PLAIN_TEXT,
30
+ * value: 'Generated by @fabasoad/sarif-to-slack'
31
+ * },
32
+ * actor: {
33
+ * include: true,
34
+ * value: 'fabasoad'
35
+ * },
36
+ * run: {
37
+ * include: true
38
+ * },
39
+ * });
40
+ * await service.sendAll();
41
+ * ```
42
+ *
43
+ * @see {@link SarifToSlackService}
44
+ *
45
+ * @packageDocumentation
46
+ */
47
+
48
+ import type { Log } from 'sarif';
49
+
50
+ /**
51
+ * Enum representing how to calculate results.
52
+ * @public
53
+ */
54
+ export declare enum CalculateResultsBy {
55
+ /**
56
+ * Calculates results by the security level of the findings: Error, Warning,
57
+ * Note and Unknown. At first, it tries to get the security level from runs[].results[].level
58
+ * property. If it is not defined, it tries to get the security level from the
59
+ * respective rule of each result, using the rules[].properties['problem.severity']
60
+ * property.
61
+ */
62
+ Level = 0,
63
+ /**
64
+ * Calculates results by the security severity of the findings: Critical, High,
65
+ * Medium, Low, None and Unknown. it tries to get the security severity from the
66
+ * respective rule of each result, using the rules[].properties['security-severity']
67
+ * property. This property contains CVSS score, which is then mapped to the
68
+ * security severity value.
69
+ */
70
+ Severity = 1
71
+ }
72
+
73
+ /**
74
+ * Options for the footer of a Slack message. "type" is ignored if "value" is
75
+ * not defined.
76
+ * @public
77
+ */
78
+ export declare type FooterOptions = IncludeAwareWithValueOptions & {
79
+ type?: FooterType;
80
+ };
81
+
82
+ /**
83
+ * Enum representing the type of footer in a Slack message.
84
+ * @public
85
+ */
86
+ export declare enum FooterType {
87
+ /**
88
+ * Represents a plain text footer. Text is not formatted and appears as-is.
89
+ */
90
+ PlainText = "plain_text",
91
+ /**
92
+ * Represents a footer with Markdown formatting. Text can include formatting
93
+ * such as bold, italics, and links.
94
+ */
95
+ Markdown = "mrkdwn"
96
+ }
97
+
98
+ /**
99
+ * Enum representing how to group results.
100
+ * @public
101
+ */
102
+ export declare enum GroupResultsBy {
103
+ /**
104
+ * Groups results by the tool name. Particularly, groups by the runs[].tool.driver.name
105
+ * property from the SARIF file(s).
106
+ */
107
+ ToolName = 0,
108
+ /**
109
+ * Groups results by the run. It provides the result from each run individually.
110
+ */
111
+ Run = 1,
112
+ /**
113
+ * Does not group results. It provides the result from all the runs from all
114
+ * the provided SARIF files.
115
+ */
116
+ Total = 2
117
+ }
118
+
119
+ /**
120
+ * Type representing properties that indicate whether to include certain information
121
+ * in the Slack message.
122
+ * @public
123
+ */
124
+ export declare type IncludeAwareOptions = {
125
+ include: boolean;
126
+ };
127
+
128
+ /**
129
+ * Type representing properties that indicate whether to include certain information
130
+ * in the Slack message, along with an optional value.
131
+ * @public
132
+ */
133
+ export declare type IncludeAwareWithValueOptions = IncludeAwareOptions & {
134
+ value?: string;
135
+ };
136
+
137
+ /**
138
+ * Enum representing log levels for the service.
139
+ * @public
140
+ */
141
+ export declare enum LogLevel {
142
+ /**
143
+ * Represents the most verbose logging level, typically used for detailed
144
+ * debugging information.
145
+ */
146
+ Silly = 0,
147
+ /**
148
+ * Represents a logging level for tracing the flow of the application.
149
+ */
150
+ Trace = 1,
151
+ /**
152
+ * Represents a logging level for debugging information that is less verbose
153
+ * than silly.
154
+ */
155
+ Debug = 2,
156
+ /**
157
+ * Represents a logging level for general informational messages that highlight
158
+ * the progress of the application.
159
+ */
160
+ Info = 3,
161
+ /**
162
+ * Represents a logging level for potentially harmful situations that require
163
+ * attention.
164
+ */
165
+ Warning = 4,
166
+ /**
167
+ * Represents a logging level for error conditions that do not require immediate
168
+ * action but should be noted.
169
+ */
170
+ Error = 5,
171
+ /**
172
+ * Represents a logging level for critical errors that require immediate attention
173
+ * and may cause the application to terminate.
174
+ */
175
+ Fatal = 6
176
+ }
177
+
178
+ /**
179
+ * Options for logging.
180
+ * @public
181
+ */
182
+ export declare type LogOptions = {
183
+ level?: LogLevel;
184
+ /**
185
+ * More details here: https://github.com/fullstack-build/tslog?tab=readme-ov-file#pretty-templates-and-styles-color-settings
186
+ */
187
+ template?: string;
188
+ colored?: boolean;
189
+ };
190
+
191
+ /**
192
+ * Type representing a SARIF log.
193
+ * @public
194
+ */
195
+ export declare type SarifLog = Log;
196
+
197
+ /**
198
+ * Options for how to output the results in the Slack message.
199
+ * @public
200
+ */
201
+ export declare type SarifToSlackOutput = {
202
+ groupBy?: GroupResultsBy;
203
+ calculateBy?: CalculateResultsBy;
204
+ };
205
+
206
+ /**
207
+ * Service to convert SARIF files to Slack messages and send them.
208
+ * @public
209
+ */
210
+ export declare class SarifToSlackService {
211
+ private readonly _slackMessages;
212
+ private constructor();
213
+ /**
214
+ * Gets the Slack messages prepared for each SARIF file.
215
+ * @returns A read-only map where keys are SARIF file paths and values are SlackMessage instances.
216
+ * @public
217
+ */
218
+ get slackMessages(): ReadonlyMap<string, SlackMessage>;
219
+ /**
220
+ * Creates an instance of SarifToSlackService.
221
+ * @param opts - Options for the service, including webhook URL, SARIF path, and other configurations.
222
+ * @returns A promise that resolves to an instance of SarifToSlackService.
223
+ * @throws Error if no SARIF files are found at the provided path.
224
+ * @public
225
+ */
226
+ static create(opts: SarifToSlackServiceOptions): Promise<SarifToSlackService>;
227
+ /**
228
+ * Sends all prepared Slack messages.
229
+ * @returns A promise that resolves when all messages have been sent.
230
+ * @throws Error if a Slack message was not prepared for a SARIF path.
231
+ * @public
232
+ */
233
+ sendAll(): Promise<void>;
234
+ /**
235
+ * Sends a Slack message for a specific SARIF path.
236
+ * @param sarifPath - The path of the SARIF file for which the message should be sent.
237
+ * @returns A promise that resolves when the message has been sent.
238
+ * @throws Error if a Slack message was not prepared for the given SARIF path.
239
+ * @public
240
+ */
241
+ send(sarifPath: string): Promise<void>;
242
+ }
243
+
244
+ /**
245
+ * Options for the SarifToSlackService.
246
+ * @public
247
+ */
248
+ export declare type SarifToSlackServiceOptions = {
249
+ webhookUrl: string;
250
+ sarifPath: string;
251
+ username?: string;
252
+ iconUrl?: string;
253
+ color?: string;
254
+ log?: LogOptions;
255
+ header?: IncludeAwareWithValueOptions;
256
+ footer?: FooterOptions;
257
+ actor?: IncludeAwareWithValueOptions;
258
+ run?: IncludeAwareOptions;
259
+ output?: SarifToSlackOutput;
260
+ };
261
+
262
+ /**
263
+ * Interface for a Slack message that can be sent.
264
+ * @public
265
+ */
266
+ export declare interface SlackMessage {
267
+ /**
268
+ * Sends the Slack message.
269
+ * @returns A promise that resolves to the response from the Slack webhook.
270
+ */
271
+ send: () => Promise<string>;
272
+ /**
273
+ * The SARIF log associated with this Slack message.
274
+ */
275
+ sarif: SarifLog;
276
+ }
277
+
278
+ export { }
@@ -1,11 +1,11 @@
1
- // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
- // It should be published with your NPM package. It should not be tracked by Git.
3
- {
4
- "tsdocVersion": "0.12",
5
- "toolPackages": [
6
- {
7
- "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.52.9"
9
- }
10
- ]
11
- }
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.52.9"
9
+ }
10
+ ]
11
+ }
package/dist/version.js CHANGED
@@ -7,5 +7,5 @@
7
7
  *
8
8
  * @internal
9
9
  */
10
- export const LIB_VERSION = '0.2.2';
10
+ export const LIB_VERSION = '0.2.3';
11
11
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy92ZXJzaW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFHLE9BQU8sQ0FBQSJ9
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@fabasoad/sarif-to-slack",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "TypeScript library to send results of SARIF file to Slack webhook URL.",
5
- "main": "dist/index.js",
5
+ "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
7
7
  "typings": "dist/index.d.ts",
8
8
  "private": false,
@@ -12,9 +12,8 @@
12
12
  "test:integration": "jest --config=jest.config.json --testNamePattern=integration",
13
13
  "clean": "rm -rf coverage && rm -rf temp",
14
14
  "clean:unsafe": "rm -f package-lock.json && rm -rf node_modules && rm -rf dist && rm -rf lib",
15
- "tsc": "tsc",
16
15
  "prebuild": "./scripts/save-version.sh",
17
- "build": "npm run tsc && api-extractor run --local --verbose",
16
+ "build": "./scripts/build.sh",
18
17
  "prepublishOnly": "npm run build",
19
18
  "preinstall": "./scripts/save-version.sh",
20
19
  "version:patch": "npm version patch --commit-hooks --git-tag-version --message 'chore: bump to version %s'",
@@ -56,6 +55,7 @@
56
55
  "jest": "30.0.5",
57
56
  "jest-circus": "30.0.5",
58
57
  "ts-jest": "29.4.0",
58
+ "tsup": "8.5.0",
59
59
  "typescript": "5.8.3"
60
60
  }
61
61
  }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env sh
2
+
3
+ main() {
4
+ tsc
5
+ api-extractor run --local --verbose
6
+ tsup src/index.ts --format cjs --target es2024 --out-dir dist-cjs --clean
7
+ mv dist-cjs/index.js dist/index.cjs
8
+ rm -rf dist-cjs
9
+ }
10
+
11
+ main "$@"
package/src/version.ts CHANGED
@@ -7,4 +7,4 @@
7
7
  *
8
8
  * @internal
9
9
  */
10
- export const LIB_VERSION = '0.2.2'
10
+ export const LIB_VERSION = '0.2.3'