@herdctl/core 0.0.2 → 0.2.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/LICENSE +21 -0
- package/dist/config/__tests__/agent.test.js +30 -0
- package/dist/config/__tests__/agent.test.js.map +1 -1
- package/dist/config/__tests__/merge.test.js +1 -1
- package/dist/config/__tests__/merge.test.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +1005 -3
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +87 -4
- package/dist/config/schema.js.map +1 -1
- package/dist/fleet-manager/__tests__/coverage.test.js +6 -2
- package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/integration.test.js +5 -0
- package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/job-control.test.js +13 -14
- package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js +13 -3
- package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/status-queries.test.js +6 -0
- package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/trigger.test.js +10 -2
- package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -1
- package/dist/fleet-manager/config-reload.d.ts +1 -1
- package/dist/fleet-manager/config-reload.js +1 -1
- package/dist/fleet-manager/fleet-manager.d.ts +1 -0
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.js +1 -0
- package/dist/fleet-manager/fleet-manager.js.map +1 -1
- package/dist/fleet-manager/job-control.d.ts +41 -0
- package/dist/fleet-manager/job-control.d.ts.map +1 -1
- package/dist/fleet-manager/job-control.js +243 -20
- package/dist/fleet-manager/job-control.js.map +1 -1
- package/dist/fleet-manager/schedule-executor.d.ts +20 -0
- package/dist/fleet-manager/schedule-executor.d.ts.map +1 -1
- package/dist/fleet-manager/schedule-executor.js +113 -3
- package/dist/fleet-manager/schedule-executor.js.map +1 -1
- package/dist/fleet-manager/types.d.ts +18 -0
- package/dist/fleet-manager/types.d.ts.map +1 -1
- package/dist/hooks/__tests__/discord-runner.test.d.ts +5 -0
- package/dist/hooks/__tests__/discord-runner.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/discord-runner.test.js +606 -0
- package/dist/hooks/__tests__/discord-runner.test.js.map +1 -0
- package/dist/hooks/__tests__/hook-executor.test.d.ts +5 -0
- package/dist/hooks/__tests__/hook-executor.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/hook-executor.test.js +443 -0
- package/dist/hooks/__tests__/hook-executor.test.js.map +1 -0
- package/dist/hooks/__tests__/shell-runner.test.d.ts +5 -0
- package/dist/hooks/__tests__/shell-runner.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/shell-runner.test.js +201 -0
- package/dist/hooks/__tests__/shell-runner.test.js.map +1 -0
- package/dist/hooks/__tests__/webhook-runner.test.d.ts +5 -0
- package/dist/hooks/__tests__/webhook-runner.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/webhook-runner.test.js +453 -0
- package/dist/hooks/__tests__/webhook-runner.test.js.map +1 -0
- package/dist/hooks/hook-executor.d.ts +129 -0
- package/dist/hooks/hook-executor.d.ts.map +1 -0
- package/dist/hooks/hook-executor.js +195 -0
- package/dist/hooks/hook-executor.js.map +1 -0
- package/dist/hooks/index.d.ts +15 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +18 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/runners/discord.d.ts +66 -0
- package/dist/hooks/runners/discord.d.ts.map +1 -0
- package/dist/hooks/runners/discord.js +294 -0
- package/dist/hooks/runners/discord.js.map +1 -0
- package/dist/hooks/runners/shell.d.ts +71 -0
- package/dist/hooks/runners/shell.d.ts.map +1 -0
- package/dist/hooks/runners/shell.js +177 -0
- package/dist/hooks/runners/shell.js.map +1 -0
- package/dist/hooks/runners/webhook.d.ts +66 -0
- package/dist/hooks/runners/webhook.d.ts.map +1 -0
- package/dist/hooks/runners/webhook.js +163 -0
- package/dist/hooks/runners/webhook.js.map +1 -0
- package/dist/hooks/types.d.ts +196 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +12 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/runner/__tests__/sdk-adapter.test.js +4 -3
- package/dist/runner/__tests__/sdk-adapter.test.js.map +1 -1
- package/dist/runner/message-processor.d.ts +5 -1
- package/dist/runner/message-processor.d.ts.map +1 -1
- package/dist/runner/message-processor.js +238 -18
- package/dist/runner/message-processor.js.map +1 -1
- package/dist/runner/sdk-adapter.d.ts.map +1 -1
- package/dist/runner/sdk-adapter.js +8 -1
- package/dist/runner/sdk-adapter.js.map +1 -1
- package/dist/runner/types.d.ts +23 -2
- package/dist/runner/types.d.ts.map +1 -1
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/scheduler.js +9 -0
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/state/schemas/job-metadata.d.ts +4 -4
- package/package.json +1 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Hook Runner
|
|
3
|
+
*
|
|
4
|
+
* Posts job notifications to a Discord channel using embeds.
|
|
5
|
+
* Used for team visibility into fleet activity.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Default timeout for Discord API requests in milliseconds
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_TIMEOUT = 10000;
|
|
11
|
+
/**
|
|
12
|
+
* Maximum output length to include in embed (Discord has a 4096 char limit for embed descriptions)
|
|
13
|
+
*/
|
|
14
|
+
const MAX_OUTPUT_LENGTH = 1000;
|
|
15
|
+
/**
|
|
16
|
+
* Embed colors for different event types
|
|
17
|
+
*/
|
|
18
|
+
const EMBED_COLORS = {
|
|
19
|
+
completed: 0x22c55e, // green
|
|
20
|
+
failed: 0xef4444, // red
|
|
21
|
+
timeout: 0xf59e0b, // amber
|
|
22
|
+
cancelled: 0x6b7280, // gray
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Truncates a string to a maximum length, adding ellipsis if truncated
|
|
26
|
+
*/
|
|
27
|
+
function truncateOutput(output, maxLength) {
|
|
28
|
+
if (output.length <= maxLength) {
|
|
29
|
+
return output;
|
|
30
|
+
}
|
|
31
|
+
return output.slice(0, maxLength - 3) + "...";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Formats duration in milliseconds to a human-readable string
|
|
35
|
+
*/
|
|
36
|
+
function formatDuration(ms) {
|
|
37
|
+
if (ms < 1000) {
|
|
38
|
+
return `${ms}ms`;
|
|
39
|
+
}
|
|
40
|
+
const seconds = Math.floor(ms / 1000);
|
|
41
|
+
if (seconds < 60) {
|
|
42
|
+
return `${seconds}s`;
|
|
43
|
+
}
|
|
44
|
+
const minutes = Math.floor(seconds / 60);
|
|
45
|
+
const remainingSeconds = seconds % 60;
|
|
46
|
+
if (minutes < 60) {
|
|
47
|
+
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
|
|
48
|
+
}
|
|
49
|
+
const hours = Math.floor(minutes / 60);
|
|
50
|
+
const remainingMinutes = minutes % 60;
|
|
51
|
+
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Gets the title emoji and text for the event
|
|
55
|
+
*/
|
|
56
|
+
function getEventTitle(event) {
|
|
57
|
+
switch (event) {
|
|
58
|
+
case "completed":
|
|
59
|
+
return "✅ Job Completed";
|
|
60
|
+
case "failed":
|
|
61
|
+
return "❌ Job Failed";
|
|
62
|
+
case "timeout":
|
|
63
|
+
return "⏱️ Job Timed Out";
|
|
64
|
+
case "cancelled":
|
|
65
|
+
return "🚫 Job Cancelled";
|
|
66
|
+
default:
|
|
67
|
+
return "📋 Job Event";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Builds a Discord embed from the hook context
|
|
72
|
+
*/
|
|
73
|
+
function buildEmbed(context) {
|
|
74
|
+
const fields = [
|
|
75
|
+
{
|
|
76
|
+
name: "Agent",
|
|
77
|
+
value: context.agent.name || context.agent.id,
|
|
78
|
+
inline: true,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "Job ID",
|
|
82
|
+
value: `\`${context.job.id}\``,
|
|
83
|
+
inline: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "Duration",
|
|
87
|
+
value: formatDuration(context.job.durationMs),
|
|
88
|
+
inline: true,
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
// Add schedule name if present
|
|
92
|
+
if (context.job.scheduleName) {
|
|
93
|
+
fields.push({
|
|
94
|
+
name: "Schedule",
|
|
95
|
+
value: context.job.scheduleName,
|
|
96
|
+
inline: true,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// Add error message if present
|
|
100
|
+
if (context.result.error) {
|
|
101
|
+
fields.push({
|
|
102
|
+
name: "Error",
|
|
103
|
+
value: `\`\`\`\n${truncateOutput(context.result.error, 500)}\n\`\`\``,
|
|
104
|
+
inline: false,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Add metadata JSON if present
|
|
108
|
+
if (context.metadata && Object.keys(context.metadata).length > 0) {
|
|
109
|
+
fields.push({
|
|
110
|
+
name: "Metadata",
|
|
111
|
+
value: `\`\`\`json\n${truncateOutput(JSON.stringify(context.metadata, null, 2), MAX_OUTPUT_LENGTH)}\n\`\`\``,
|
|
112
|
+
inline: false,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// Add output preview if present and meaningful
|
|
116
|
+
const output = context.result.output.trim();
|
|
117
|
+
if (output && output.length > 0) {
|
|
118
|
+
fields.push({
|
|
119
|
+
name: "Output",
|
|
120
|
+
value: `\`\`\`\n${truncateOutput(output, MAX_OUTPUT_LENGTH)}\n\`\`\``,
|
|
121
|
+
inline: false,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
title: getEventTitle(context.event),
|
|
126
|
+
color: EMBED_COLORS[context.event] ?? EMBED_COLORS.completed,
|
|
127
|
+
fields,
|
|
128
|
+
timestamp: context.job.completedAt,
|
|
129
|
+
footer: {
|
|
130
|
+
text: "herdctl",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* DiscordHookRunner posts job notifications to a Discord channel
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const runner = new DiscordHookRunner({ logger: console });
|
|
140
|
+
*
|
|
141
|
+
* const result = await runner.execute(
|
|
142
|
+
* {
|
|
143
|
+
* type: 'discord',
|
|
144
|
+
* channel_id: '1234567890',
|
|
145
|
+
* bot_token_env: 'DISCORD_BOT_TOKEN'
|
|
146
|
+
* },
|
|
147
|
+
* hookContext
|
|
148
|
+
* );
|
|
149
|
+
*
|
|
150
|
+
* if (result.success) {
|
|
151
|
+
* console.log('Discord notification sent');
|
|
152
|
+
* } else {
|
|
153
|
+
* console.error('Discord notification failed:', result.error);
|
|
154
|
+
* }
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export class DiscordHookRunner {
|
|
158
|
+
logger;
|
|
159
|
+
fetchFn;
|
|
160
|
+
constructor(options = {}) {
|
|
161
|
+
this.logger = options.logger ?? {
|
|
162
|
+
debug: () => { },
|
|
163
|
+
info: () => { },
|
|
164
|
+
warn: () => { },
|
|
165
|
+
error: () => { },
|
|
166
|
+
};
|
|
167
|
+
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Execute a Discord hook with the given context
|
|
171
|
+
*
|
|
172
|
+
* @param config - Discord hook configuration (accepts input type with optional fields)
|
|
173
|
+
* @param context - Hook context to send in the notification
|
|
174
|
+
* @returns Promise resolving to the hook result
|
|
175
|
+
*/
|
|
176
|
+
async execute(config, context) {
|
|
177
|
+
const startTime = Date.now();
|
|
178
|
+
this.logger.debug(`Executing Discord hook for channel: ${config.channel_id}`);
|
|
179
|
+
// Read bot token from environment variable
|
|
180
|
+
const botToken = process.env[config.bot_token_env];
|
|
181
|
+
if (!botToken) {
|
|
182
|
+
const durationMs = Date.now() - startTime;
|
|
183
|
+
const errorMessage = `Discord bot token not found in environment variable: ${config.bot_token_env}`;
|
|
184
|
+
this.logger.error(errorMessage);
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
hookType: "discord",
|
|
188
|
+
durationMs,
|
|
189
|
+
error: errorMessage,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
// Build the Discord embed
|
|
194
|
+
const embed = buildEmbed(context);
|
|
195
|
+
const payload = {
|
|
196
|
+
embeds: [embed],
|
|
197
|
+
};
|
|
198
|
+
// Discord API endpoint for posting messages
|
|
199
|
+
const url = `https://discord.com/api/v10/channels/${config.channel_id}/messages`;
|
|
200
|
+
// Create abort controller for timeout
|
|
201
|
+
const controller = new AbortController();
|
|
202
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
203
|
+
try {
|
|
204
|
+
const response = await this.fetchFn(url, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: {
|
|
207
|
+
"Content-Type": "application/json",
|
|
208
|
+
Authorization: `Bot ${botToken}`,
|
|
209
|
+
},
|
|
210
|
+
body: JSON.stringify(payload),
|
|
211
|
+
signal: controller.signal,
|
|
212
|
+
});
|
|
213
|
+
clearTimeout(timeoutId);
|
|
214
|
+
const durationMs = Date.now() - startTime;
|
|
215
|
+
// Read response body for logging/debugging
|
|
216
|
+
let responseBody;
|
|
217
|
+
try {
|
|
218
|
+
responseBody = await response.text();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Ignore response body read errors
|
|
222
|
+
}
|
|
223
|
+
// 2xx status codes are success
|
|
224
|
+
if (response.ok) {
|
|
225
|
+
this.logger.info(`Discord hook completed successfully in ${durationMs}ms: channel ${config.channel_id} (${response.status})`);
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
hookType: "discord",
|
|
229
|
+
durationMs,
|
|
230
|
+
output: responseBody,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Parse Discord API error
|
|
235
|
+
let errorDetail = `HTTP ${response.status}: ${response.statusText}`;
|
|
236
|
+
if (responseBody) {
|
|
237
|
+
try {
|
|
238
|
+
const errorJson = JSON.parse(responseBody);
|
|
239
|
+
if (errorJson.message) {
|
|
240
|
+
errorDetail = `Discord API error: ${errorJson.message} (code: ${errorJson.code || response.status})`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
errorDetail += ` - ${responseBody}`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
this.logger.warn(`Discord hook failed with status ${response.status}: ${errorDetail}`);
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
hookType: "discord",
|
|
251
|
+
durationMs,
|
|
252
|
+
error: errorDetail,
|
|
253
|
+
output: responseBody,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (fetchError) {
|
|
258
|
+
clearTimeout(timeoutId);
|
|
259
|
+
const durationMs = Date.now() - startTime;
|
|
260
|
+
// Handle abort (timeout)
|
|
261
|
+
if (fetchError instanceof Error && fetchError.name === "AbortError") {
|
|
262
|
+
this.logger.error(`Discord hook timed out after ${DEFAULT_TIMEOUT}ms`);
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
hookType: "discord",
|
|
266
|
+
durationMs,
|
|
267
|
+
error: `Discord hook timed out after ${DEFAULT_TIMEOUT}ms`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// Handle other fetch errors (network errors, etc.)
|
|
271
|
+
const errorMessage = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
272
|
+
this.logger.error(`Discord hook error: ${errorMessage}`);
|
|
273
|
+
return {
|
|
274
|
+
success: false,
|
|
275
|
+
hookType: "discord",
|
|
276
|
+
durationMs,
|
|
277
|
+
error: errorMessage,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
const durationMs = Date.now() - startTime;
|
|
283
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
284
|
+
this.logger.error(`Discord hook error: ${errorMessage}`);
|
|
285
|
+
return {
|
|
286
|
+
success: false,
|
|
287
|
+
hookType: "discord",
|
|
288
|
+
durationMs,
|
|
289
|
+
error: errorMessage,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../../../src/hooks/runners/discord.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Hook Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes shell commands with HookContext JSON piped to stdin.
|
|
5
|
+
* Used for integrating with custom scripts, logging, and external tooling.
|
|
6
|
+
*/
|
|
7
|
+
import type { HookContext, HookResult, ShellHookConfigInput } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Logger interface for ShellHookRunner
|
|
10
|
+
*/
|
|
11
|
+
export interface ShellHookRunnerLogger {
|
|
12
|
+
debug: (message: string) => void;
|
|
13
|
+
info: (message: string) => void;
|
|
14
|
+
warn: (message: string) => void;
|
|
15
|
+
error: (message: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Options for ShellHookRunner
|
|
19
|
+
*/
|
|
20
|
+
export interface ShellHookRunnerOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Logger for hook execution output
|
|
23
|
+
*/
|
|
24
|
+
logger?: ShellHookRunnerLogger;
|
|
25
|
+
/**
|
|
26
|
+
* Working directory for shell commands
|
|
27
|
+
*/
|
|
28
|
+
cwd?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Additional environment variables to pass to the shell
|
|
31
|
+
*/
|
|
32
|
+
env?: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* ShellHookRunner executes shell commands with HookContext on stdin
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const runner = new ShellHookRunner({ logger: console });
|
|
40
|
+
*
|
|
41
|
+
* const result = await runner.execute(
|
|
42
|
+
* { type: 'shell', command: './scripts/log-job.sh' },
|
|
43
|
+
* hookContext
|
|
44
|
+
* );
|
|
45
|
+
*
|
|
46
|
+
* if (result.success) {
|
|
47
|
+
* console.log('Hook completed:', result.output);
|
|
48
|
+
* } else {
|
|
49
|
+
* console.error('Hook failed:', result.error);
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare class ShellHookRunner {
|
|
54
|
+
private logger;
|
|
55
|
+
private cwd?;
|
|
56
|
+
private env?;
|
|
57
|
+
constructor(options?: ShellHookRunnerOptions);
|
|
58
|
+
/**
|
|
59
|
+
* Execute a shell hook with the given context
|
|
60
|
+
*
|
|
61
|
+
* @param config - Shell hook configuration (accepts input type with optional fields)
|
|
62
|
+
* @param context - Hook context to pass to the script
|
|
63
|
+
* @returns Promise resolving to the hook result
|
|
64
|
+
*/
|
|
65
|
+
execute(config: ShellHookConfigInput, context: HookContext): Promise<HookResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Run the shell command and capture output
|
|
68
|
+
*/
|
|
69
|
+
private runCommand;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/hooks/runners/shell.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAajF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAE/B;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,GAAG,CAAC,CAAS;IACrB,OAAO,CAAC,GAAG,CAAC,CAAyB;gBAEzB,OAAO,GAAE,sBAA2B;IAWhD;;;;;;OAMG;IACG,OAAO,CAAC,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IA+CtF;;OAEG;IACH,OAAO,CAAC,UAAU;CAyFnB"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Hook Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes shell commands with HookContext JSON piped to stdin.
|
|
5
|
+
* Used for integrating with custom scripts, logging, and external tooling.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
/**
|
|
9
|
+
* Default timeout for shell hooks in milliseconds
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_TIMEOUT = 30000;
|
|
12
|
+
/**
|
|
13
|
+
* Maximum output buffer size in bytes (1MB)
|
|
14
|
+
* Prevents memory issues with verbose scripts
|
|
15
|
+
*/
|
|
16
|
+
const MAX_OUTPUT_SIZE = 1024 * 1024;
|
|
17
|
+
/**
|
|
18
|
+
* ShellHookRunner executes shell commands with HookContext on stdin
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const runner = new ShellHookRunner({ logger: console });
|
|
23
|
+
*
|
|
24
|
+
* const result = await runner.execute(
|
|
25
|
+
* { type: 'shell', command: './scripts/log-job.sh' },
|
|
26
|
+
* hookContext
|
|
27
|
+
* );
|
|
28
|
+
*
|
|
29
|
+
* if (result.success) {
|
|
30
|
+
* console.log('Hook completed:', result.output);
|
|
31
|
+
* } else {
|
|
32
|
+
* console.error('Hook failed:', result.error);
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class ShellHookRunner {
|
|
37
|
+
logger;
|
|
38
|
+
cwd;
|
|
39
|
+
env;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.logger = options.logger ?? {
|
|
42
|
+
debug: () => { },
|
|
43
|
+
info: () => { },
|
|
44
|
+
warn: () => { },
|
|
45
|
+
error: () => { },
|
|
46
|
+
};
|
|
47
|
+
this.cwd = options.cwd;
|
|
48
|
+
this.env = options.env;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Execute a shell hook with the given context
|
|
52
|
+
*
|
|
53
|
+
* @param config - Shell hook configuration (accepts input type with optional fields)
|
|
54
|
+
* @param context - Hook context to pass to the script
|
|
55
|
+
* @returns Promise resolving to the hook result
|
|
56
|
+
*/
|
|
57
|
+
async execute(config, context) {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
60
|
+
const hookName = config.name ?? "shell hook";
|
|
61
|
+
this.logger.debug(`Executing ${hookName}`);
|
|
62
|
+
try {
|
|
63
|
+
const result = await this.runCommand(config.command, context, timeout);
|
|
64
|
+
const durationMs = Date.now() - startTime;
|
|
65
|
+
if (result.exitCode === 0) {
|
|
66
|
+
this.logger.info(`${hookName} completed in ${durationMs}ms`);
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
hookType: "shell",
|
|
70
|
+
durationMs,
|
|
71
|
+
output: result.stdout,
|
|
72
|
+
exitCode: result.exitCode,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this.logger.warn(`${hookName} failed with exit code ${result.exitCode}`);
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
hookType: "shell",
|
|
80
|
+
durationMs,
|
|
81
|
+
error: `Exit code ${result.exitCode}: ${result.stderr || "No error output"}`,
|
|
82
|
+
output: result.stdout,
|
|
83
|
+
exitCode: result.exitCode,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const durationMs = Date.now() - startTime;
|
|
89
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
90
|
+
this.logger.error(`${hookName} error: ${errorMessage}`);
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
hookType: "shell",
|
|
94
|
+
durationMs,
|
|
95
|
+
error: errorMessage,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Run the shell command and capture output
|
|
101
|
+
*/
|
|
102
|
+
runCommand(command, context, timeout) {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const contextJson = JSON.stringify(context);
|
|
105
|
+
// Spawn shell process
|
|
106
|
+
const proc = spawn(command, {
|
|
107
|
+
shell: true,
|
|
108
|
+
cwd: this.cwd,
|
|
109
|
+
env: {
|
|
110
|
+
...process.env,
|
|
111
|
+
...this.env,
|
|
112
|
+
},
|
|
113
|
+
// Don't inherit stdio - we want to capture output
|
|
114
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
115
|
+
});
|
|
116
|
+
let stdout = "";
|
|
117
|
+
let stderr = "";
|
|
118
|
+
let killed = false;
|
|
119
|
+
// Set up timeout
|
|
120
|
+
const timeoutHandle = setTimeout(() => {
|
|
121
|
+
killed = true;
|
|
122
|
+
proc.kill("SIGTERM");
|
|
123
|
+
// Force kill after 1 second if SIGTERM doesn't work
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
if (!proc.killed) {
|
|
126
|
+
proc.kill("SIGKILL");
|
|
127
|
+
}
|
|
128
|
+
}, 1000);
|
|
129
|
+
}, timeout);
|
|
130
|
+
// Capture stdout
|
|
131
|
+
proc.stdout?.on("data", (data) => {
|
|
132
|
+
const chunk = data.toString();
|
|
133
|
+
if (stdout.length + chunk.length <= MAX_OUTPUT_SIZE) {
|
|
134
|
+
stdout += chunk;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
// Capture stderr
|
|
138
|
+
proc.stderr?.on("data", (data) => {
|
|
139
|
+
const chunk = data.toString();
|
|
140
|
+
if (stderr.length + chunk.length <= MAX_OUTPUT_SIZE) {
|
|
141
|
+
stderr += chunk;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
// Handle process exit
|
|
145
|
+
proc.on("close", (code) => {
|
|
146
|
+
clearTimeout(timeoutHandle);
|
|
147
|
+
if (killed) {
|
|
148
|
+
reject(new Error(`Hook timed out after ${timeout}ms`));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
resolve({
|
|
152
|
+
exitCode: code ?? 1,
|
|
153
|
+
stdout: stdout.trim(),
|
|
154
|
+
stderr: stderr.trim(),
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
// Handle spawn errors
|
|
158
|
+
proc.on("error", (error) => {
|
|
159
|
+
clearTimeout(timeoutHandle);
|
|
160
|
+
reject(error);
|
|
161
|
+
});
|
|
162
|
+
// Write context to stdin and close it
|
|
163
|
+
// Handle EPIPE errors that occur if process exits before we finish writing
|
|
164
|
+
if (proc.stdin) {
|
|
165
|
+
proc.stdin.on("error", (err) => {
|
|
166
|
+
// EPIPE is expected if process exits early - ignore it
|
|
167
|
+
if (err.code !== "EPIPE") {
|
|
168
|
+
this.logger.warn(`stdin error: ${err.message}`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
proc.stdin.write(contextJson);
|
|
172
|
+
proc.stdin.end();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/hooks/runners/shell.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook Hook Runner
|
|
3
|
+
*
|
|
4
|
+
* POSTs HookContext JSON to a configured URL.
|
|
5
|
+
* Used for integrating with external services (monitoring, ticketing, dashboards).
|
|
6
|
+
*/
|
|
7
|
+
import type { HookContext, HookResult, WebhookHookConfigInput } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Logger interface for WebhookHookRunner
|
|
10
|
+
*/
|
|
11
|
+
export interface WebhookHookRunnerLogger {
|
|
12
|
+
debug: (message: string) => void;
|
|
13
|
+
info: (message: string) => void;
|
|
14
|
+
warn: (message: string) => void;
|
|
15
|
+
error: (message: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Options for WebhookHookRunner
|
|
19
|
+
*/
|
|
20
|
+
export interface WebhookHookRunnerOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Logger for hook execution output
|
|
23
|
+
*/
|
|
24
|
+
logger?: WebhookHookRunnerLogger;
|
|
25
|
+
/**
|
|
26
|
+
* Custom fetch implementation (for testing)
|
|
27
|
+
*/
|
|
28
|
+
fetch?: typeof globalThis.fetch;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* WebhookHookRunner POSTs HookContext JSON to a URL
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const runner = new WebhookHookRunner({ logger: console });
|
|
36
|
+
*
|
|
37
|
+
* const result = await runner.execute(
|
|
38
|
+
* {
|
|
39
|
+
* type: 'webhook',
|
|
40
|
+
* url: 'https://api.example.com/hooks/job-complete',
|
|
41
|
+
* headers: { 'Authorization': 'Bearer ${API_TOKEN}' }
|
|
42
|
+
* },
|
|
43
|
+
* hookContext
|
|
44
|
+
* );
|
|
45
|
+
*
|
|
46
|
+
* if (result.success) {
|
|
47
|
+
* console.log('Webhook delivered successfully');
|
|
48
|
+
* } else {
|
|
49
|
+
* console.error('Webhook failed:', result.error);
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare class WebhookHookRunner {
|
|
54
|
+
private logger;
|
|
55
|
+
private fetchFn;
|
|
56
|
+
constructor(options?: WebhookHookRunnerOptions);
|
|
57
|
+
/**
|
|
58
|
+
* Execute a webhook hook with the given context
|
|
59
|
+
*
|
|
60
|
+
* @param config - Webhook hook configuration (accepts input type with optional fields)
|
|
61
|
+
* @param context - Hook context to send in the request body
|
|
62
|
+
* @returns Promise resolving to the hook result
|
|
63
|
+
*/
|
|
64
|
+
execute(config: WebhookHookConfigInput, context: HookContext): Promise<HookResult>;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/hooks/runners/webhook.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAOnF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAEjC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAmBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAA0B;gBAE7B,OAAO,GAAE,wBAA6B;IAUlD;;;;;;OAMG;IACG,OAAO,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;CA2GzF"}
|