@theholocron/holocron-plugin-discord 3.25.1 → 3.26.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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ <!-- editorconfig-checker-disable-file -->
2
+
3
+ # `@theholocron/holocron-plugin-discord`
4
+
5
+ Discord plugin for [Holocron](../cli). Implements the `notifications`
6
+ capability via [Discord incoming webhooks](https://discord.com/developers/docs/resources/webhook).
7
+
8
+ ## Install
9
+
10
+ <!-- prettier-ignore -->
11
+ ```bash
12
+ pnpm add -D @theholocron/holocron-plugin-discord
13
+
14
+ ```
15
+
16
+ ## Auth
17
+
18
+ Token resolution order:
19
+
20
+ 1. `--token <WEBHOOK_URL>` flag on the holocron invocation
21
+ 2. `HOLOCRON_DISCORD_WEBHOOK` env var
22
+ 3. `DISCORD_WEBHOOK_URL` env var
23
+
24
+ The "token" is the full incoming webhook URL
25
+ (`https://discord.com/api/webhooks/{id}/{token}`). No bot account or
26
+ server membership is required — generate one in Discord under
27
+ **channel settings → Integrations → Webhooks**.
28
+
29
+ ## Config
30
+
31
+ <!-- prettier-ignore -->
32
+ ```jsonc
33
+ {
34
+ "providers": {
35
+ "notifications": [
36
+ "discord",
37
+ {
38
+ "webhooks": { "deploys": "https://discord.com/api/webhooks/…" },
39
+ "defaultChannel": "deploys",
40
+ },
41
+ ],
42
+ },
43
+ }
44
+
45
+ ```
46
+
47
+ - `webhooks` (optional) — map of logical channel names to webhook URLs.
48
+ Allows `send("deploys", msg)` instead of embedding raw URLs in call sites.
49
+ - `defaultChannel` (optional) — alias key or raw webhook URL used when
50
+ `send()` is called with an empty string. Defaults to the resolved token
51
+ (the webhook URL from the env var / keyring).
52
+
53
+ ## What's implemented
54
+
55
+ | Method | What it does |
56
+ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
+ | `send` | Posts a message to the given channel. `channel` can be a named alias (from `webhooks`), a raw webhook URL, or an empty string (falls back to `defaultChannel`). Returns on `204 No Content`. |
58
+
59
+ The webhook id and token are embedded in the URL path — no `Authorization`
60
+ header is sent. `parseWebhookUrl` is exported as a utility for callers
61
+ that need to split the URL into its parts.
@@ -0,0 +1,89 @@
1
+ import { AuthError, Notifications, ResolveTokenInput } from "@theholocron/cli";
2
+ //#region src/auth.d.ts
3
+ declare const resolveToken: (input?: ResolveTokenInput) => string;
4
+ //#endregion
5
+ //#region src/rest.d.ts
6
+ interface DiscordClientOptions {
7
+ /** Override base URL for tests. Default: https://discord.com/api/v10 */
8
+ baseUrl?: string;
9
+ fetch?: typeof fetch;
10
+ }
11
+ interface DiscordWebhookInfo {
12
+ id: string;
13
+ name: string;
14
+ guild_id?: string;
15
+ }
16
+ interface DiscordClient {
17
+ webhooks: {
18
+ get(id: string, token: string): Promise<DiscordWebhookInfo>;
19
+ execute(id: string, token: string, content: string): Promise<void>;
20
+ };
21
+ }
22
+ declare function createDiscordClient({ baseUrl, fetch: fetchImpl }?: DiscordClientOptions): DiscordClient;
23
+ /** Parse a Discord webhook URL into its id and token parts. */
24
+ declare function parseWebhookUrl(url: string): {
25
+ id: string;
26
+ token: string;
27
+ };
28
+ //#endregion
29
+ //#region src/capabilities/notifications.d.ts
30
+ interface DiscordNotificationsOptions {
31
+ /**
32
+ * Named aliases mapping a logical channel name to its webhook URL.
33
+ * Allows `send("deploys", msg)` instead of passing the full URL.
34
+ */
35
+ webhooks?: Record<string, string>;
36
+ /**
37
+ * Default webhook URL (or alias key) used when `send()` is called
38
+ * without an explicit channel, or with an empty string.
39
+ */
40
+ defaultChannel?: string;
41
+ }
42
+ declare class DiscordNotifications implements Notifications {
43
+ private readonly client;
44
+ private readonly opts;
45
+ readonly key: "notifications";
46
+ readonly providerName = "discord";
47
+ constructor(client: DiscordClient, opts: DiscordNotificationsOptions);
48
+ send(channel: string, message: string): Promise<void>;
49
+ private resolve;
50
+ }
51
+ //#endregion
52
+ //#region src/verify-token.d.ts
53
+ interface VerifyTokenSuccess {
54
+ ok: true;
55
+ subject: string;
56
+ }
57
+ interface VerifyTokenFailure {
58
+ ok: false;
59
+ message: string;
60
+ }
61
+ type VerifyTokenResult = VerifyTokenSuccess | VerifyTokenFailure;
62
+ interface VerifyTokenOptions {
63
+ baseUrl?: string;
64
+ fetch?: typeof fetch;
65
+ }
66
+ declare function verifyToken(webhookUrl: string, opts?: VerifyTokenOptions): Promise<VerifyTokenResult>;
67
+ //#endregion
68
+ //#region src/index.d.ts
69
+ interface DiscordPluginOptions extends ResolveTokenInput, DiscordNotificationsOptions {
70
+ /** Override base URL for tests. Default: https://discord.com/api/v10 */
71
+ baseUrl?: string;
72
+ fetch?: typeof fetch;
73
+ }
74
+ interface PluginContext {
75
+ options: DiscordPluginOptions;
76
+ client: DiscordClient;
77
+ defaultWebhookUrl: string;
78
+ }
79
+ declare function createContext(options?: DiscordPluginOptions): PluginContext;
80
+ declare function notifications(ctx: PluginContext): Notifications;
81
+ declare function createPlugin(options?: DiscordPluginOptions): {
82
+ name: string;
83
+ capabilities: {
84
+ notifications: () => Notifications;
85
+ };
86
+ };
87
+ declare const AUTH_HINT: string;
88
+ //#endregion
89
+ export { AUTH_HINT, AuthError, type DiscordClient, type DiscordClientOptions, DiscordNotifications, type DiscordNotificationsOptions, DiscordPluginOptions, type DiscordWebhookInfo, PluginContext, type ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createDiscordClient, createPlugin, notifications, parseWebhookUrl, resolveToken, verifyToken };
package/dist/index.mjs ADDED
@@ -0,0 +1,111 @@
1
+ import { AuthError, ProviderApiError, createResolveToken } from "@theholocron/cli";
2
+ //#region src/auth.ts
3
+ const resolveToken = createResolveToken({
4
+ envName: "HOLOCRON_DISCORD_WEBHOOK",
5
+ vendorEnvName: "DISCORD_WEBHOOK_URL",
6
+ keyringService: "discord",
7
+ errorMessage: "no Discord webhook URL found. Pass --token <WEBHOOK_URL>, set HOLOCRON_DISCORD_WEBHOOK / DISCORD_WEBHOOK_URL, or run: holocron auth set discord <https://discord.com/api/webhooks/...>"
8
+ });
9
+ //#endregion
10
+ //#region src/rest.ts
11
+ function createDiscordClient({ baseUrl, fetch: fetchImpl } = {}) {
12
+ const base = baseUrl ?? "https://discord.com/api/v10";
13
+ const f = fetchImpl ?? globalThis.fetch;
14
+ return { webhooks: {
15
+ get: async (id, token) => {
16
+ const res = await f(`${base}/webhooks/${id}/${token}`, { method: "GET" });
17
+ if (!res.ok) throw new ProviderApiError(`Discord webhook not found or invalid (${res.status})`, res.status, void 0);
18
+ return res.json();
19
+ },
20
+ execute: async (id, token, content) => {
21
+ const res = await f(`${base}/webhooks/${id}/${token}`, {
22
+ method: "POST",
23
+ headers: { "content-type": "application/json" },
24
+ body: JSON.stringify({ content })
25
+ });
26
+ if (!res.ok) throw new ProviderApiError(`Discord webhook POST failed (${res.status})`, res.status, void 0);
27
+ }
28
+ } };
29
+ }
30
+ /** Parse a Discord webhook URL into its id and token parts. */
31
+ function parseWebhookUrl(url) {
32
+ const match = url.match(/webhooks\/(\d+)\/([^/?#]+)/);
33
+ if (!match) throw new Error(`Invalid Discord webhook URL: ${url}`);
34
+ return {
35
+ id: match[1],
36
+ token: match[2]
37
+ };
38
+ }
39
+ //#endregion
40
+ //#region src/capabilities/notifications.ts
41
+ var DiscordNotifications = class {
42
+ client;
43
+ opts;
44
+ key = "notifications";
45
+ providerName = "discord";
46
+ constructor(client, opts) {
47
+ this.client = client;
48
+ this.opts = opts;
49
+ }
50
+ async send(channel, message) {
51
+ const { id, token } = parseWebhookUrl(this.resolve(channel || (this.opts.defaultChannel ?? "")));
52
+ await this.client.webhooks.execute(id, token, message);
53
+ }
54
+ resolve(channel) {
55
+ const alias = this.opts.webhooks?.[channel];
56
+ if (alias) return alias;
57
+ if (channel.startsWith("https://")) return channel;
58
+ const def = this.opts.defaultChannel;
59
+ if (def) return this.opts.webhooks?.[def] ?? def;
60
+ throw new Error(`DiscordNotifications.send: unknown channel "${channel}" — pass a webhook URL, an alias key, or set defaultChannel`);
61
+ }
62
+ };
63
+ //#endregion
64
+ //#region src/verify-token.ts
65
+ async function verifyToken(webhookUrl, opts = {}) {
66
+ try {
67
+ const { id, token } = parseWebhookUrl(webhookUrl);
68
+ const info = await createDiscordClient({
69
+ baseUrl: opts.baseUrl,
70
+ fetch: opts.fetch
71
+ }).webhooks.get(id, token);
72
+ return {
73
+ ok: true,
74
+ subject: `webhook: ${info.name} (id: ${info.id})`
75
+ };
76
+ } catch (err) {
77
+ return {
78
+ ok: false,
79
+ message: err instanceof Error ? err.message : String(err)
80
+ };
81
+ }
82
+ }
83
+ //#endregion
84
+ //#region src/index.ts
85
+ function createContext(options = {}) {
86
+ const defaultWebhookUrl = resolveToken(options);
87
+ return {
88
+ options,
89
+ client: createDiscordClient({
90
+ baseUrl: options.baseUrl,
91
+ fetch: options.fetch
92
+ }),
93
+ defaultWebhookUrl
94
+ };
95
+ }
96
+ function notifications(ctx) {
97
+ return new DiscordNotifications(ctx.client, {
98
+ ...ctx.options,
99
+ defaultChannel: ctx.options.defaultChannel ?? ctx.defaultWebhookUrl
100
+ });
101
+ }
102
+ function createPlugin(options = {}) {
103
+ const ctx = createContext(options);
104
+ return {
105
+ name: "@theholocron/holocron-plugin-discord",
106
+ capabilities: { notifications: () => notifications(ctx) }
107
+ };
108
+ }
109
+ const AUTH_HINT = "create a webhook in Discord: open your server → channel settings → Integrations → Webhooks → New Webhook, copy the webhook URL, then run: holocron auth set discord <https://discord.com/api/webhooks/...>";
110
+ //#endregion
111
+ export { AUTH_HINT, AuthError, DiscordNotifications, createContext, createDiscordClient, createPlugin, notifications, parseWebhookUrl, resolveToken, verifyToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-discord",
3
- "version": "3.25.1",
3
+ "version": "3.26.0",
4
4
  "description": "Holocron plugin for Discord. Implements the notifications capability via Discord incoming webhooks.",
5
5
  "homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-discord#readme",
6
6
  "bugs": "https://github.com/theholocron/holocron/issues",
@@ -21,7 +21,7 @@
21
21
  }
22
22
  },
23
23
  "peerDependencies": {
24
- "@theholocron/cli": "3.25.1"
24
+ "@theholocron/cli": "3.26.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@theholocron/eslint-config": "^7.19.1",
@@ -39,7 +39,7 @@
39
39
  "tsx": "4.22.4",
40
40
  "typescript": "^5.9.3",
41
41
  "vitest": "^4.1.10",
42
- "@theholocron/cli": "3.25.1"
42
+ "@theholocron/cli": "3.26.0"
43
43
  },
44
44
  "publishConfig": {
45
45
  "access": "public"