dep-brain 1.5.0 → 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 +34 -10
- 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,6 +6,8 @@
|
|
|
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.6.0` adds Slack and Discord notification summaries while keeping analysis output contract `1.6`.
|
|
10
|
+
|
|
9
11
|
## Vision
|
|
10
12
|
|
|
11
13
|
`dep-brain` aims to become a dependency decision engine:
|
|
@@ -26,6 +28,7 @@
|
|
|
26
28
|
- Generate a simple project health score
|
|
27
29
|
- Output reports in console, JSON, Markdown, SARIF, dashboard, and top-issues formats
|
|
28
30
|
- Output upgrade-advice reports via `--advise`
|
|
31
|
+
- Send Slack and Discord webhook summaries for CI runs
|
|
29
32
|
- Gate CI with score and finding policies
|
|
30
33
|
- Compare new findings against a baseline report
|
|
31
34
|
|
|
@@ -35,7 +38,7 @@ The long-term goal is not just to list problems, but to answer:
|
|
|
35
38
|
- Can I remove it safely?
|
|
36
39
|
- What should I fix first?
|
|
37
40
|
|
|
38
|
-
##
|
|
41
|
+
## 1.6 Highlights
|
|
39
42
|
|
|
40
43
|
- Duplicate dependency detection with lockfile instance tracking
|
|
41
44
|
- Unused dependency detection with runtime vs dev-tool heuristics
|
|
@@ -53,6 +56,7 @@ The long-term goal is not just to list problems, but to answer:
|
|
|
53
56
|
- SARIF output via `--sarif`
|
|
54
57
|
- Static HTML dashboard via `--dashboard`
|
|
55
58
|
- Upgrade advisor output via `--advise`
|
|
59
|
+
- Slack and Discord notification summaries via `--notify`
|
|
56
60
|
- Ranked top issues via `--top`
|
|
57
61
|
- Baseline mode via `--baseline`
|
|
58
62
|
- Focused analysis via `--focus`
|
|
@@ -82,6 +86,8 @@ npx dep-brain analyze --json --out depbrain.json
|
|
|
82
86
|
npx dep-brain analyze --sarif --out depbrain.sarif
|
|
83
87
|
npx dep-brain analyze --dashboard
|
|
84
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
|
|
85
91
|
npx dep-brain analyze --focus duplicates
|
|
86
92
|
npx dep-brain analyze --ci
|
|
87
93
|
npx dep-brain analyze --baseline depbrain-baseline.json
|
|
@@ -167,7 +173,9 @@ Suggestions:
|
|
|
167
173
|
dep-brain analyze --json
|
|
168
174
|
```
|
|
169
175
|
|
|
170
|
-
Output includes `outputVersion` for schema stability
|
|
176
|
+
Output includes `outputVersion` for schema stability. `dep-brain@1.6.0` writes contract version `1.6`.
|
|
177
|
+
|
|
178
|
+
Validate against:
|
|
171
179
|
|
|
172
180
|
- `depbrain.output.schema.json`
|
|
173
181
|
|
|
@@ -202,6 +210,15 @@ dep-brain analyze --dashboard --dashboard-out reports/depbrain.html
|
|
|
202
210
|
|
|
203
211
|
Writes a static HTML dashboard. Default path comes from `dashboard.outputPath`.
|
|
204
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
|
+
|
|
205
222
|
## Plugins
|
|
206
223
|
|
|
207
224
|
```json
|
|
@@ -272,6 +289,8 @@ Create a `depbrain.config.json` file in the project root:
|
|
|
272
289
|
"outputPath": "depbrain-dashboard.html"
|
|
273
290
|
},
|
|
274
291
|
"notifications": {
|
|
292
|
+
"enabled": false,
|
|
293
|
+
"on": "failure",
|
|
275
294
|
"slackWebhookEnv": "DEPBRAIN_SLACK_WEBHOOK_URL",
|
|
276
295
|
"discordWebhookEnv": "DEPBRAIN_DISCORD_WEBHOOK_URL"
|
|
277
296
|
},
|
|
@@ -311,6 +330,8 @@ Supported sections:
|
|
|
311
330
|
- `risk.lowTrustWeightThreshold`
|
|
312
331
|
- `risk.mediumTrustWeightThreshold`
|
|
313
332
|
- `dashboard.outputPath`
|
|
333
|
+
- `notifications.enabled`
|
|
334
|
+
- `notifications.on`
|
|
314
335
|
- `notifications.slackWebhookEnv`
|
|
315
336
|
- `notifications.discordWebhookEnv`
|
|
316
337
|
- `scoring.duplicateWeight`
|
|
@@ -338,6 +359,7 @@ dep-brain analyze --fail-on-unused
|
|
|
338
359
|
dep-brain analyze --min-score 85 --fail-on-risks
|
|
339
360
|
dep-brain analyze --config depbrain.config.json
|
|
340
361
|
dep-brain analyze --baseline depbrain-baseline.json --fail-on-unused
|
|
362
|
+
dep-brain analyze --ci --notify
|
|
341
363
|
```
|
|
342
364
|
|
|
343
365
|
## Config Debugging
|
|
@@ -384,17 +406,19 @@ src/
|
|
|
384
406
|
|
|
385
407
|
## Product Direction
|
|
386
408
|
|
|
387
|
-
`dep-brain` is in
|
|
409
|
+
`dep-brain` is in `v1.6.0` production CLI stage, with current focus on actionable dependency decisions and CI workflow integration.
|
|
410
|
+
|
|
411
|
+
Recent releases added:
|
|
388
412
|
|
|
389
|
-
-
|
|
390
|
-
-
|
|
391
|
-
-
|
|
392
|
-
-
|
|
393
|
-
-
|
|
413
|
+
- transitive risk ownership and path tracing
|
|
414
|
+
- dashboard and plugin support
|
|
415
|
+
- baseline, focus, and CI workflows
|
|
416
|
+
- structured upgrade advice with release-note links
|
|
417
|
+
- Slack and Discord notification summaries
|
|
394
418
|
|
|
395
|
-
|
|
419
|
+
Project should optimize for trust, clarity, and actionability over flashy UI, generic graphs, or simply adding more checks.
|
|
396
420
|
|
|
397
|
-
Risk findings
|
|
421
|
+
Risk findings include `trustScore`, structured `riskFactors`, `transitiveRiskScore`, and `riskyTransitiveDeps` path traces so teams can see which direct package introduces supply-chain risk.
|
|
398
422
|
|
|
399
423
|
## Repository Notes
|
|
400
424
|
|
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
|
+
}
|