dep-brain 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +23 -4
- package/action.yml +14 -0
- package/depbrain.config.json +2 -0
- package/depbrain.config.schema.json +2 -0
- package/dist/cli.js +30 -2
- package/dist/core/analyzer.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +9 -0
- package/dist/utils/notifications.d.ts +20 -0
- package/dist/utils/notifications.js +116 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.6.0
|
|
6
|
+
|
|
7
|
+
- Added Slack and Discord webhook notification summaries.
|
|
8
|
+
- Added `--notify` and `--notify-on` CLI controls.
|
|
9
|
+
- Added notification config with `enabled`, `on`, `slackWebhookEnv`, and `discordWebhookEnv`.
|
|
10
|
+
- Added GitHub Action inputs for notification runs.
|
|
11
|
+
- Kept analysis output contract at `1.6` because no JSON result fields changed.
|
|
12
|
+
|
|
5
13
|
## 1.5.0
|
|
6
14
|
|
|
7
15
|
- Added structured upgrade advisor data under `outdated[].advice`.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
`dep-brain` is a CLI and library for explainable dependency intelligence in JavaScript and TypeScript projects.
|
|
8
8
|
|
|
9
|
-
Current release `1.
|
|
9
|
+
Current release `1.6.0` adds Slack and Discord notification summaries while keeping analysis output contract `1.6`.
|
|
10
10
|
|
|
11
11
|
## Vision
|
|
12
12
|
|
|
@@ -28,6 +28,7 @@ Current release `1.5.1` adds upgrade-advice output, stepped major-version guidan
|
|
|
28
28
|
- Generate a simple project health score
|
|
29
29
|
- Output reports in console, JSON, Markdown, SARIF, dashboard, and top-issues formats
|
|
30
30
|
- Output upgrade-advice reports via `--advise`
|
|
31
|
+
- Send Slack and Discord webhook summaries for CI runs
|
|
31
32
|
- Gate CI with score and finding policies
|
|
32
33
|
- Compare new findings against a baseline report
|
|
33
34
|
|
|
@@ -37,7 +38,7 @@ The long-term goal is not just to list problems, but to answer:
|
|
|
37
38
|
- Can I remove it safely?
|
|
38
39
|
- What should I fix first?
|
|
39
40
|
|
|
40
|
-
## 1.
|
|
41
|
+
## 1.6 Highlights
|
|
41
42
|
|
|
42
43
|
- Duplicate dependency detection with lockfile instance tracking
|
|
43
44
|
- Unused dependency detection with runtime vs dev-tool heuristics
|
|
@@ -55,6 +56,7 @@ The long-term goal is not just to list problems, but to answer:
|
|
|
55
56
|
- SARIF output via `--sarif`
|
|
56
57
|
- Static HTML dashboard via `--dashboard`
|
|
57
58
|
- Upgrade advisor output via `--advise`
|
|
59
|
+
- Slack and Discord notification summaries via `--notify`
|
|
58
60
|
- Ranked top issues via `--top`
|
|
59
61
|
- Baseline mode via `--baseline`
|
|
60
62
|
- Focused analysis via `--focus`
|
|
@@ -84,6 +86,8 @@ npx dep-brain analyze --json --out depbrain.json
|
|
|
84
86
|
npx dep-brain analyze --sarif --out depbrain.sarif
|
|
85
87
|
npx dep-brain analyze --dashboard
|
|
86
88
|
npx dep-brain analyze --dashboard --dashboard-out reports/depbrain.html
|
|
89
|
+
npx dep-brain analyze --notify
|
|
90
|
+
npx dep-brain analyze --notify --notify-on always
|
|
87
91
|
npx dep-brain analyze --focus duplicates
|
|
88
92
|
npx dep-brain analyze --ci
|
|
89
93
|
npx dep-brain analyze --baseline depbrain-baseline.json
|
|
@@ -169,7 +173,7 @@ Suggestions:
|
|
|
169
173
|
dep-brain analyze --json
|
|
170
174
|
```
|
|
171
175
|
|
|
172
|
-
Output includes `outputVersion` for schema stability. `dep-brain@1.
|
|
176
|
+
Output includes `outputVersion` for schema stability. `dep-brain@1.6.0` writes contract version `1.6`.
|
|
173
177
|
|
|
174
178
|
Validate against:
|
|
175
179
|
|
|
@@ -206,6 +210,15 @@ dep-brain analyze --dashboard --dashboard-out reports/depbrain.html
|
|
|
206
210
|
|
|
207
211
|
Writes a static HTML dashboard. Default path comes from `dashboard.outputPath`.
|
|
208
212
|
|
|
213
|
+
## Notifications
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
DEPBRAIN_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/... dep-brain analyze --notify
|
|
217
|
+
DEPBRAIN_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... dep-brain analyze --notify --notify-on always
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`--notify` sends compact summaries to configured Slack and Discord webhook URLs. Default trigger is `failure`, so passing runs stay quiet unless `--notify-on always` is set.
|
|
221
|
+
|
|
209
222
|
## Plugins
|
|
210
223
|
|
|
211
224
|
```json
|
|
@@ -276,6 +289,8 @@ Create a `depbrain.config.json` file in the project root:
|
|
|
276
289
|
"outputPath": "depbrain-dashboard.html"
|
|
277
290
|
},
|
|
278
291
|
"notifications": {
|
|
292
|
+
"enabled": false,
|
|
293
|
+
"on": "failure",
|
|
279
294
|
"slackWebhookEnv": "DEPBRAIN_SLACK_WEBHOOK_URL",
|
|
280
295
|
"discordWebhookEnv": "DEPBRAIN_DISCORD_WEBHOOK_URL"
|
|
281
296
|
},
|
|
@@ -315,6 +330,8 @@ Supported sections:
|
|
|
315
330
|
- `risk.lowTrustWeightThreshold`
|
|
316
331
|
- `risk.mediumTrustWeightThreshold`
|
|
317
332
|
- `dashboard.outputPath`
|
|
333
|
+
- `notifications.enabled`
|
|
334
|
+
- `notifications.on`
|
|
318
335
|
- `notifications.slackWebhookEnv`
|
|
319
336
|
- `notifications.discordWebhookEnv`
|
|
320
337
|
- `scoring.duplicateWeight`
|
|
@@ -342,6 +359,7 @@ dep-brain analyze --fail-on-unused
|
|
|
342
359
|
dep-brain analyze --min-score 85 --fail-on-risks
|
|
343
360
|
dep-brain analyze --config depbrain.config.json
|
|
344
361
|
dep-brain analyze --baseline depbrain-baseline.json --fail-on-unused
|
|
362
|
+
dep-brain analyze --ci --notify
|
|
345
363
|
```
|
|
346
364
|
|
|
347
365
|
## Config Debugging
|
|
@@ -388,7 +406,7 @@ src/
|
|
|
388
406
|
|
|
389
407
|
## Product Direction
|
|
390
408
|
|
|
391
|
-
`dep-brain` is in `v1.
|
|
409
|
+
`dep-brain` is in `v1.6.0` production CLI stage, with current focus on actionable dependency decisions and CI workflow integration.
|
|
392
410
|
|
|
393
411
|
Recent releases added:
|
|
394
412
|
|
|
@@ -396,6 +414,7 @@ Recent releases added:
|
|
|
396
414
|
- dashboard and plugin support
|
|
397
415
|
- baseline, focus, and CI workflows
|
|
398
416
|
- structured upgrade advice with release-note links
|
|
417
|
+
- Slack and Discord notification summaries
|
|
399
418
|
|
|
400
419
|
Project should optimize for trust, clarity, and actionability over flashy UI, generic graphs, or simply adding more checks.
|
|
401
420
|
|
package/action.yml
CHANGED
|
@@ -51,6 +51,14 @@ inputs:
|
|
|
51
51
|
description: Fail when risky dependencies are found.
|
|
52
52
|
required: false
|
|
53
53
|
default: "false"
|
|
54
|
+
notify:
|
|
55
|
+
description: Send Slack or Discord webhook summaries.
|
|
56
|
+
required: false
|
|
57
|
+
default: "false"
|
|
58
|
+
notify-on:
|
|
59
|
+
description: Notification trigger. Use always, failure, or never.
|
|
60
|
+
required: false
|
|
61
|
+
default: "failure"
|
|
54
62
|
|
|
55
63
|
runs:
|
|
56
64
|
using: composite
|
|
@@ -70,6 +78,8 @@ runs:
|
|
|
70
78
|
INPUT_FAIL_ON_OUTDATED: ${{ inputs.fail-on-outdated }}
|
|
71
79
|
INPUT_FAIL_ON_DUPLICATES: ${{ inputs.fail-on-duplicates }}
|
|
72
80
|
INPUT_FAIL_ON_RISKS: ${{ inputs.fail-on-risks }}
|
|
81
|
+
INPUT_NOTIFY: ${{ inputs.notify }}
|
|
82
|
+
INPUT_NOTIFY_ON: ${{ inputs.notify-on }}
|
|
73
83
|
run: |
|
|
74
84
|
set -euo pipefail
|
|
75
85
|
|
|
@@ -127,6 +137,10 @@ runs:
|
|
|
127
137
|
args+=("--fail-on-risks")
|
|
128
138
|
fi
|
|
129
139
|
|
|
140
|
+
if [ "$INPUT_NOTIFY" = "true" ]; then
|
|
141
|
+
args+=("--notify" "--notify-on" "$INPUT_NOTIFY_ON")
|
|
142
|
+
fi
|
|
143
|
+
|
|
130
144
|
node "$GITHUB_ACTION_PATH/dist/cli.js" "${args[@]}"
|
|
131
145
|
|
|
132
146
|
branding:
|
package/depbrain.config.json
CHANGED
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
"type": "object",
|
|
69
69
|
"additionalProperties": false,
|
|
70
70
|
"properties": {
|
|
71
|
+
"enabled": { "type": "boolean" },
|
|
72
|
+
"on": { "type": "string", "enum": ["always", "failure", "never"] },
|
|
71
73
|
"slackWebhookEnv": { "type": "string" },
|
|
72
74
|
"discordWebhookEnv": { "type": "string" }
|
|
73
75
|
}
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { renderJsonReport } from "./reporters/json.js";
|
|
|
5
5
|
import { renderMarkdownReport } from "./reporters/markdown.js";
|
|
6
6
|
import { renderSarifReport } from "./reporters/sarif.js";
|
|
7
7
|
import { renderDashboardReport } from "./reporters/dashboard.js";
|
|
8
|
+
import { sendConfiguredNotifications } from "./utils/notifications.js";
|
|
8
9
|
import { defaultConfig } from "./utils/config.js";
|
|
9
10
|
import { promises as fs } from "node:fs";
|
|
10
11
|
import path from "node:path";
|
|
@@ -160,6 +161,20 @@ async function main() {
|
|
|
160
161
|
if (flags.has("--dashboard")) {
|
|
161
162
|
await writeOutput(renderDashboardReport(result), optionValues.get("--dashboard-out") ?? result.config.dashboard.outputPath);
|
|
162
163
|
}
|
|
164
|
+
if (result.config.notifications.enabled) {
|
|
165
|
+
const notificationResults = await sendConfiguredNotifications(result);
|
|
166
|
+
for (const item of notificationResults) {
|
|
167
|
+
if (item.status === "sent") {
|
|
168
|
+
console.error(`Notification sent to ${item.channel}.`);
|
|
169
|
+
}
|
|
170
|
+
else if (item.status === "failed") {
|
|
171
|
+
console.error(`Notification failed for ${item.channel}: ${item.reason ?? "unknown error"}`);
|
|
172
|
+
}
|
|
173
|
+
else if (item.reason?.includes("webhook env")) {
|
|
174
|
+
console.error(`Notification skipped for ${item.channel}: ${item.reason}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
163
178
|
if (!result.policy.passed) {
|
|
164
179
|
process.exitCode = 1;
|
|
165
180
|
}
|
|
@@ -183,6 +198,7 @@ void main();
|
|
|
183
198
|
function buildCliConfig(flags, optionValues) {
|
|
184
199
|
const minScore = optionValues.get("--min-score");
|
|
185
200
|
const policy = {};
|
|
201
|
+
const notifications = {};
|
|
186
202
|
if (minScore) {
|
|
187
203
|
policy.minScore = Number(minScore);
|
|
188
204
|
}
|
|
@@ -203,8 +219,18 @@ function buildCliConfig(flags, optionValues) {
|
|
|
203
219
|
policy.failOnDuplicates = true;
|
|
204
220
|
policy.failOnRisks = true;
|
|
205
221
|
}
|
|
222
|
+
if (flags.has("--notify")) {
|
|
223
|
+
notifications.enabled = true;
|
|
224
|
+
}
|
|
225
|
+
const notifyOn = optionValues.get("--notify-on");
|
|
226
|
+
if (notifyOn === "always" ||
|
|
227
|
+
notifyOn === "failure" ||
|
|
228
|
+
notifyOn === "never") {
|
|
229
|
+
notifications.on = notifyOn;
|
|
230
|
+
}
|
|
206
231
|
return {
|
|
207
|
-
policy
|
|
232
|
+
policy,
|
|
233
|
+
notifications
|
|
208
234
|
};
|
|
209
235
|
}
|
|
210
236
|
async function hasPackageJson(targetPath) {
|
|
@@ -220,7 +246,7 @@ function printHelp() {
|
|
|
220
246
|
console.log("Dependency Brain");
|
|
221
247
|
console.log("");
|
|
222
248
|
console.log("Usage:");
|
|
223
|
-
console.log(" dep-brain analyze [path] [--json] [--md] [--sarif] [--top] [--dashboard] [--focus kind] [--ci] [--out path] [--config path] [--baseline path] [--min-score n] [--fail-on-risks]");
|
|
249
|
+
console.log(" dep-brain analyze [path] [--json] [--md] [--sarif] [--top] [--dashboard] [--notify] [--notify-on kind] [--focus kind] [--ci] [--out path] [--config path] [--baseline path] [--min-score n] [--fail-on-risks]");
|
|
224
250
|
console.log(" dep-brain report --from <file> [--md] [--json] [--sarif] [--top] [--advise] [--dashboard] [--out path]");
|
|
225
251
|
console.log(" dep-brain config [path] [--config path]");
|
|
226
252
|
console.log(" dep-brain init [--out depbrain.config.json]");
|
|
@@ -235,6 +261,8 @@ function printHelp() {
|
|
|
235
261
|
console.log(" --advise Output upgrade advice for outdated dependencies");
|
|
236
262
|
console.log(" --dashboard Write an HTML dashboard");
|
|
237
263
|
console.log(" --dashboard-out <path> Write dashboard HTML to a custom path");
|
|
264
|
+
console.log(" --notify Send Slack or Discord webhook summaries");
|
|
265
|
+
console.log(" --notify-on <kind> Send notifications on always, failure, or never");
|
|
238
266
|
console.log(" --focus <kind> Run all, health, duplicates, unused, outdated, or risks");
|
|
239
267
|
console.log(" --ci Apply low-noise CI defaults");
|
|
240
268
|
console.log(" --config <path> Path to depbrain.config.json");
|
package/dist/core/analyzer.js
CHANGED
|
@@ -154,6 +154,8 @@ function mergeConfig(base, overrides) {
|
|
|
154
154
|
outputPath: overrides.dashboard?.outputPath ?? base.dashboard.outputPath
|
|
155
155
|
},
|
|
156
156
|
notifications: {
|
|
157
|
+
enabled: overrides.notifications?.enabled ?? base.notifications.enabled,
|
|
158
|
+
on: overrides.notifications?.on ?? base.notifications.on,
|
|
157
159
|
slackWebhookEnv: overrides.notifications?.slackWebhookEnv ?? base.notifications.slackWebhookEnv,
|
|
158
160
|
discordWebhookEnv: overrides.notifications?.discordWebhookEnv ?? base.notifications.discordWebhookEnv
|
|
159
161
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ export { PluginManager } from "./core/plugin-manager.js";
|
|
|
5
5
|
export type { DepBrainPlugin, PluginDiagnostic, ProjectContext } from "./core/plugin-manager.js";
|
|
6
6
|
export type { AnalysisContext, CheckResult, Issue } from "./core/types.js";
|
|
7
7
|
export type { DepBrainConfig, DepBrainConfigOverrides } from "./utils/config.js";
|
|
8
|
+
export { renderNotificationMessage, sendConfiguredNotifications, shouldSendNotification } from "./utils/notifications.js";
|
|
9
|
+
export type { NotificationChannel, NotificationResult, NotificationSendInput, NotificationSender } from "./utils/notifications.js";
|
|
8
10
|
export type { WorkspacePackage } from "./utils/workspaces.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { analyzeProject } from "./core/analyzer.js";
|
|
2
2
|
export { OUTPUT_VERSION } from "./core/analyzer.js";
|
|
3
3
|
export { PluginManager } from "./core/plugin-manager.js";
|
|
4
|
+
export { renderNotificationMessage, sendConfiguredNotifications, shouldSendNotification } from "./utils/notifications.js";
|
package/dist/utils/config.d.ts
CHANGED
package/dist/utils/config.js
CHANGED
|
@@ -38,6 +38,8 @@ export const defaultConfig = {
|
|
|
38
38
|
outputPath: "depbrain-dashboard.html"
|
|
39
39
|
},
|
|
40
40
|
notifications: {
|
|
41
|
+
enabled: false,
|
|
42
|
+
on: "failure",
|
|
41
43
|
slackWebhookEnv: "DEPBRAIN_SLACK_WEBHOOK_URL",
|
|
42
44
|
discordWebhookEnv: "DEPBRAIN_DISCORD_WEBHOOK_URL"
|
|
43
45
|
},
|
|
@@ -100,6 +102,8 @@ function normalizeConfig(loaded) {
|
|
|
100
102
|
outputPath: normalizeString(loaded.dashboard?.outputPath, defaultConfig.dashboard.outputPath)
|
|
101
103
|
},
|
|
102
104
|
notifications: {
|
|
105
|
+
enabled: normalizeBoolean(loaded.notifications?.enabled, defaultConfig.notifications.enabled),
|
|
106
|
+
on: normalizeNotificationTrigger(loaded.notifications?.on, defaultConfig.notifications.on),
|
|
103
107
|
slackWebhookEnv: normalizeString(loaded.notifications?.slackWebhookEnv, defaultConfig.notifications.slackWebhookEnv),
|
|
104
108
|
discordWebhookEnv: normalizeString(loaded.notifications?.discordWebhookEnv, defaultConfig.notifications.discordWebhookEnv)
|
|
105
109
|
},
|
|
@@ -129,3 +133,8 @@ function normalizeNumber(value, fallback) {
|
|
|
129
133
|
function normalizeString(value, fallback) {
|
|
130
134
|
return typeof value === "string" && value.trim().length > 0 ? value : fallback;
|
|
131
135
|
}
|
|
136
|
+
function normalizeNotificationTrigger(value, fallback) {
|
|
137
|
+
return value === "always" || value === "failure" || value === "never"
|
|
138
|
+
? value
|
|
139
|
+
: fallback;
|
|
140
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AnalysisResult } from "../core/analyzer.js";
|
|
2
|
+
import type { DepBrainConfig } from "./config.js";
|
|
3
|
+
export type NotificationChannel = "slack" | "discord";
|
|
4
|
+
export interface NotificationSendInput {
|
|
5
|
+
channel: NotificationChannel;
|
|
6
|
+
webhookUrl: string;
|
|
7
|
+
payload: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface NotificationResult {
|
|
10
|
+
channel: NotificationChannel;
|
|
11
|
+
status: "sent" | "skipped" | "failed";
|
|
12
|
+
reason?: string;
|
|
13
|
+
}
|
|
14
|
+
export type NotificationSender = (input: NotificationSendInput) => Promise<void>;
|
|
15
|
+
export declare function sendConfiguredNotifications(result: AnalysisResult, options?: {
|
|
16
|
+
env?: Record<string, string | undefined>;
|
|
17
|
+
sender?: NotificationSender;
|
|
18
|
+
}): Promise<NotificationResult[]>;
|
|
19
|
+
export declare function shouldSendNotification(result: AnalysisResult, config: DepBrainConfig["notifications"]): boolean;
|
|
20
|
+
export declare function renderNotificationMessage(result: AnalysisResult): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export async function sendConfiguredNotifications(result, options = {}) {
|
|
2
|
+
const config = result.config.notifications;
|
|
3
|
+
if (!shouldSendNotification(result, config)) {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
channel: "slack",
|
|
7
|
+
status: "skipped",
|
|
8
|
+
reason: "notification trigger did not match analysis result"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
channel: "discord",
|
|
12
|
+
status: "skipped",
|
|
13
|
+
reason: "notification trigger did not match analysis result"
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
const env = options.env ?? process.env;
|
|
18
|
+
const sender = options.sender ?? postWebhook;
|
|
19
|
+
const message = renderNotificationMessage(result);
|
|
20
|
+
const targets = [
|
|
21
|
+
{
|
|
22
|
+
channel: "slack",
|
|
23
|
+
webhookUrl: env[config.slackWebhookEnv],
|
|
24
|
+
payload: { text: message }
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
channel: "discord",
|
|
28
|
+
webhookUrl: env[config.discordWebhookEnv],
|
|
29
|
+
payload: { content: message }
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
const results = [];
|
|
33
|
+
for (const target of targets) {
|
|
34
|
+
if (!target.webhookUrl) {
|
|
35
|
+
results.push({
|
|
36
|
+
channel: target.channel,
|
|
37
|
+
status: "skipped",
|
|
38
|
+
reason: `${target.channel} webhook env is not set`
|
|
39
|
+
});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
await sender({
|
|
44
|
+
channel: target.channel,
|
|
45
|
+
webhookUrl: target.webhookUrl,
|
|
46
|
+
payload: target.payload
|
|
47
|
+
});
|
|
48
|
+
results.push({ channel: target.channel, status: "sent" });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
results.push({
|
|
52
|
+
channel: target.channel,
|
|
53
|
+
status: "failed",
|
|
54
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
export function shouldSendNotification(result, config) {
|
|
61
|
+
if (!config.enabled || config.on === "never") {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return config.on === "always" || !result.policy.passed;
|
|
65
|
+
}
|
|
66
|
+
export function renderNotificationMessage(result) {
|
|
67
|
+
const status = result.policy.passed ? "PASS" : "FAIL";
|
|
68
|
+
const counts = [
|
|
69
|
+
`duplicates ${result.duplicates.length}`,
|
|
70
|
+
`unused ${result.unused.length}`,
|
|
71
|
+
`outdated ${result.outdated.length}`,
|
|
72
|
+
`risks ${result.risks.length}`
|
|
73
|
+
].join(", ");
|
|
74
|
+
const lines = [
|
|
75
|
+
`dep-brain ${status}: score ${result.score}/100`,
|
|
76
|
+
`path: ${result.rootDir}`,
|
|
77
|
+
`findings: ${counts}`
|
|
78
|
+
];
|
|
79
|
+
const upgradePriorities = buildUpgradePriorityText(result);
|
|
80
|
+
if (result.policy.reasons.length > 0) {
|
|
81
|
+
lines.push(`policy: ${result.policy.reasons.join("; ")}`);
|
|
82
|
+
}
|
|
83
|
+
if (result.topIssues.length > 0) {
|
|
84
|
+
lines.push("top issues:");
|
|
85
|
+
for (const issue of result.topIssues.slice(0, 3)) {
|
|
86
|
+
lines.push(`- ${formatTopIssue(issue)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (upgradePriorities) {
|
|
90
|
+
lines.push(`upgrades: ${upgradePriorities}`);
|
|
91
|
+
}
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
94
|
+
async function postWebhook(input) {
|
|
95
|
+
const response = await fetch(input.webhookUrl, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { "content-type": "application/json" },
|
|
98
|
+
body: JSON.stringify(input.payload)
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw new Error(`${input.channel} webhook failed with HTTP ${response.status}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function formatTopIssue(issue) {
|
|
105
|
+
const owner = issue.package ? ` [${issue.package}]` : "";
|
|
106
|
+
return `[${issue.priority}] ${issue.kind} ${issue.name}${owner}: ${issue.summary}`;
|
|
107
|
+
}
|
|
108
|
+
function buildUpgradePriorityText(result) {
|
|
109
|
+
const counts = result.outdated.reduce((acc, item) => {
|
|
110
|
+
acc[item.advice.risk] += 1;
|
|
111
|
+
return acc;
|
|
112
|
+
}, { high: 0, medium: 0, low: 0 });
|
|
113
|
+
return [`high ${counts.high}`, `medium ${counts.medium}`, `low ${counts.low}`]
|
|
114
|
+
.filter((entry) => !entry.endsWith(" 0"))
|
|
115
|
+
.join(", ");
|
|
116
|
+
}
|