also looking at this
fix: keep the low-tier promises — bulk-update label, event-name validation, workflow personalisation
#1434
9 files
+325
-13
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import type { BuddyConfig } from './types' |
| 2 | 2 | import type { GitProviderName } from './git/provider' |
| 3 | 3 | import { BUILTIN_ANALYZERS } from './analysis/engine' |
| 4 | import { EVENT_NAMES } from './events' | |
| 4 | 5 | import { IMPLEMENTED_PROVIDERS, PROVIDER_TRACKING_ISSUES } from './git/provider' |
| 5 | 6 | import { ConfigurationError } from './types' |
| 6 | 7 | |
| @@ -177,6 +178,7 @@ export function validateConfig(config: BuddyConfig): ConfigIssue[] { | ||
| 177 | 178 | validateSchedules(issues, config) |
| 178 | 179 | validateReleaseNotes(issues, config) |
| 179 | 180 | validateDashboard(issues, config) |
| 181 | validateNotifications(issues, config) | |
| 180 | 182 | validateAi(issues, config) |
| 181 | 183 | validateAnalysis(issues, config) |
| 182 | 184 | |
| @@ -688,6 +690,70 @@ function validateDashboard(issues: ConfigIssue[], config: BuddyConfig): void { | ||
| 688 | 690 | issues.push({ path: 'dashboard.title', message: `expected a non-empty string, got ${quote(dashboard.title)}` }) |
| 689 | 691 | } |
| 690 | 692 | |
| 693 | function validateNotifications(issues: ConfigIssue[], config: BuddyConfig): void { | |
| 694 | const notifications = config.notifications | |
| 695 | if (notifications === undefined) | |
| 696 | return | |
| 697 | if (!isPlainObject(notifications)) { | |
| 698 | issues.push({ path: 'notifications', message: `expected an object, got ${quote(notifications)}` }) | |
| 699 | return | |
| 700 | } | |
| 701 | ||
| 702 | // An `events` filter naming an event that does not exist subscribes to | |
| 703 | // nothing: the sink is built, the credential is read, and every | |
| 704 | // notification is silently dropped. `EVENT_NAMES` exists for exactly this | |
| 705 | // check. | |
| 706 | const checkEvents = (path: string, events: unknown): void => { | |
| 707 | if (events === undefined) | |
| 708 | return | |
| 709 | if (!Array.isArray(events)) { | |
| 710 | issues.push({ path, message: `expected an array of event names, got ${quote(events)}` }) | |
| 711 | return | |
| 712 | } | |
| 713 | events.forEach((event, index) => { | |
| 714 | checkEnum(issues, `${path}[${index}]`, event, EVENT_NAMES) | |
| 715 | }) | |
| 716 | } | |
| 717 | ||
| 718 | const checkEnvName = (path: string, value: unknown): void => { | |
| 719 | if (value !== undefined && (typeof value !== 'string' || !value.trim())) | |
| 720 | issues.push({ path, message: `expected an environment variable name, got ${quote(value)}` }) | |
| 721 | } | |
| 722 | ||
| 723 | for (const channel of ['slack', 'discord'] as const) { | |
| 724 | const target = notifications[channel] | |
| 725 | if (target === undefined) | |
| 726 | continue | |
| 727 | if (!isPlainObject(target)) { | |
| 728 | issues.push({ path: `notifications.${channel}`, message: `expected an object, got ${quote(target)}` }) | |
| 729 | continue | |
| 730 | } | |
| 731 | checkEnvName(`notifications.${channel}.webhookEnv`, target.webhookEnv) | |
| 732 | checkEvents(`notifications.${channel}.events`, target.events) | |
| 733 | } | |
| 734 | ||
| 735 | const webhooks = notifications.webhooks | |
| 736 | if (webhooks === undefined) | |
| 737 | return | |
| 738 | if (!Array.isArray(webhooks)) { | |
| 739 | issues.push({ path: 'notifications.webhooks', message: `expected an array, got ${quote(webhooks)}` }) | |
| 740 | return | |
| 741 | } | |
| 742 | webhooks.forEach((webhook, index) => { | |
| 743 | const base = `notifications.webhooks[${index}]` | |
| 744 | if (!isPlainObject(webhook)) { | |
| 745 | issues.push({ path: base, message: `expected an object, got ${quote(webhook)}` }) | |
| 746 | return | |
| 747 | } | |
| 748 | if (webhook.url === undefined) | |
| 749 | issues.push({ path: `${base}.url`, message: 'a webhook needs a url' }) | |
| 750 | else | |
| 751 | checkUrl(issues, `${base}.url`, webhook.url) | |
| 752 | checkEnvName(`${base}.secretEnv`, webhook.secretEnv) | |
| 753 | checkEvents(`${base}.events`, webhook.events) | |
| 754 | }) | |
| 755 | } | |
| 756 | ||
| 691 | 757 | /** |
| 692 | 758 | * Render validation issues as a single multi-line, human-readable block. |
| 693 | 759 | * |