@checkstack/healthcheck-script-backend 0.4.1 → 0.5.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
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# @checkstack/healthcheck-script-backend
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 35bc682: feat(healthcheck): expose check + system run-context to script collectors
|
|
8
|
+
|
|
9
|
+
Script health checks can now read which check and system a run is for.
|
|
10
|
+
Previously shell scripts got only a curated env whitelist and inline
|
|
11
|
+
scripts only `context.config`, so a script had no built-in way to know
|
|
12
|
+
its own check name or the system it was checking.
|
|
13
|
+
|
|
14
|
+
- `@checkstack/backend-api`: new `CollectorRunContext` type
|
|
15
|
+
(`{ check: { id, name, intervalSeconds }, system: { id, name } }`) and
|
|
16
|
+
an optional `runContext` param on `CollectorStrategy.execute`. Optional,
|
|
17
|
+
so existing collector implementations are unaffected.
|
|
18
|
+
- Shell-script collector: injects reserved `CHECKSTACK_CHECK_ID`,
|
|
19
|
+
`CHECKSTACK_CHECK_NAME`, `CHECKSTACK_CHECK_INTERVAL_SECONDS`,
|
|
20
|
+
`CHECKSTACK_SYSTEM_ID`, `CHECKSTACK_SYSTEM_NAME` env vars (user-supplied
|
|
21
|
+
`env` still wins on collision).
|
|
22
|
+
- Inline-script collector: exposes `context.check` and `context.system`
|
|
23
|
+
alongside `context.config`; the inline-script editor now types them for
|
|
24
|
+
autocomplete.
|
|
25
|
+
- Shell editors (health-check collectors and automation shell actions) now
|
|
26
|
+
also suggest the user's own `env` (JSON) keys as `$NAME` completions, via
|
|
27
|
+
the new exported `customShellEnvVars` helper. Keys that aren't valid shell
|
|
28
|
+
identifiers are omitted.
|
|
29
|
+
- Fix: the Typefox `CodeEditor` captured a stale `onChange` at editor start,
|
|
30
|
+
so editing one `DynamicForm` field reverted sibling fields changed since
|
|
31
|
+
mount (e.g. typing in a shell `script` field wiped an unsaved `env` value,
|
|
32
|
+
or deleted a sibling automation action added after mount). The change
|
|
33
|
+
handler now routes through a ref to the current `onChange`.
|
|
34
|
+
- Fix: focusing a JSON editor threw "LanguageStatusService.addStatus is not
|
|
35
|
+
supported" because the standalone service set omitted `ILanguageStatusService`.
|
|
36
|
+
That one service is now registered via `serviceOverrides`.
|
|
37
|
+
- Fix: the automation trigger card nested a `<Badge>` (a `<div>`) inside a
|
|
38
|
+
`<p>`, producing a `validateDOMNesting` warning. Switched the wrapper to a
|
|
39
|
+
`<div>`.
|
|
40
|
+
- Local runs (`queue-executor`) and satellite runs both populate the
|
|
41
|
+
context. `SatelliteAssignment` (and the `getAssignmentsForSatellite`
|
|
42
|
+
RPC output) gained optional `configName` / `systemName` so the metadata
|
|
43
|
+
reaches satellite-side execution; `HealthCheckService` resolves the
|
|
44
|
+
system name via the catalog client.
|
|
45
|
+
|
|
46
|
+
BREAKING CHANGE: `createHealthCheckRouter` now requires a `catalogClient`
|
|
47
|
+
option (used to resolve system names for satellite assignments). Update
|
|
48
|
+
call sites to pass the catalog RPC client.
|
|
49
|
+
|
|
50
|
+
### Patch Changes
|
|
51
|
+
|
|
52
|
+
- Updated dependencies [6d52276]
|
|
53
|
+
- Updated dependencies [35bc682]
|
|
54
|
+
- @checkstack/common@0.12.0
|
|
55
|
+
- @checkstack/backend-api@0.18.0
|
|
56
|
+
- @checkstack/healthcheck-common@1.3.0
|
|
57
|
+
|
|
58
|
+
## 0.4.2
|
|
59
|
+
|
|
60
|
+
### Patch Changes
|
|
61
|
+
|
|
62
|
+
- Updated dependencies [ba07ae2]
|
|
63
|
+
- @checkstack/healthcheck-common@1.2.0
|
|
64
|
+
- @checkstack/backend-api@0.17.1
|
|
65
|
+
|
|
3
66
|
## 0.4.1
|
|
4
67
|
|
|
5
68
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-script-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"checkstack": {
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"pack": "bunx @checkstack/scripts plugin-pack"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@checkstack/backend-api": "0.
|
|
18
|
-
"@checkstack/common": "0.
|
|
19
|
-
"@checkstack/healthcheck-common": "1.
|
|
17
|
+
"@checkstack/backend-api": "0.17.1",
|
|
18
|
+
"@checkstack/common": "0.11.0",
|
|
19
|
+
"@checkstack/healthcheck-common": "1.2.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/bun": "^1.0.0",
|
|
23
23
|
"typescript": "^5.0.0",
|
|
24
24
|
"@checkstack/tsconfig": "0.0.7",
|
|
25
|
-
"@checkstack/scripts": "0.3.
|
|
25
|
+
"@checkstack/scripts": "0.3.3"
|
|
26
26
|
},
|
|
27
27
|
"description": "Checkstack healthcheck-script-backend plugin",
|
|
28
28
|
"author": {
|
|
@@ -101,6 +101,66 @@ describe("ExecuteCollector", () => {
|
|
|
101
101
|
timeout: 3000,
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
it("injects run-context metadata into the script env", async () => {
|
|
106
|
+
const collector = new ExecuteCollector();
|
|
107
|
+
const client = createMockClient();
|
|
108
|
+
|
|
109
|
+
await collector.execute({
|
|
110
|
+
config: { script: "echo hi", timeout: 3000 },
|
|
111
|
+
client,
|
|
112
|
+
pluginId: "test",
|
|
113
|
+
runContext: {
|
|
114
|
+
check: { id: "check-1", name: "CPU load", intervalSeconds: 60 },
|
|
115
|
+
system: { id: "system-9", name: "web-01" },
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const call = (client.exec as ReturnType<typeof mock>).mock.calls[0][0];
|
|
120
|
+
expect(call.env.CHECKSTACK_CHECK_ID).toBe("check-1");
|
|
121
|
+
expect(call.env.CHECKSTACK_CHECK_NAME).toBe("CPU load");
|
|
122
|
+
expect(call.env.CHECKSTACK_CHECK_INTERVAL_SECONDS).toBe("60");
|
|
123
|
+
expect(call.env.CHECKSTACK_SYSTEM_ID).toBe("system-9");
|
|
124
|
+
expect(call.env.CHECKSTACK_SYSTEM_NAME).toBe("web-01");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("lets a user config.env key win over a metadata key on collision", async () => {
|
|
128
|
+
const collector = new ExecuteCollector();
|
|
129
|
+
const client = createMockClient();
|
|
130
|
+
|
|
131
|
+
await collector.execute({
|
|
132
|
+
config: {
|
|
133
|
+
script: "echo hi",
|
|
134
|
+
env: { CHECKSTACK_CHECK_NAME: "user override" },
|
|
135
|
+
timeout: 3000,
|
|
136
|
+
},
|
|
137
|
+
client,
|
|
138
|
+
pluginId: "test",
|
|
139
|
+
runContext: {
|
|
140
|
+
check: { id: "check-1", name: "metadata name", intervalSeconds: 60 },
|
|
141
|
+
system: { id: "system-9", name: "web-01" },
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const call = (client.exec as ReturnType<typeof mock>).mock.calls[0][0];
|
|
146
|
+
expect(call.env.CHECKSTACK_CHECK_NAME).toBe("user override");
|
|
147
|
+
// Non-colliding metadata keys are still present.
|
|
148
|
+
expect(call.env.CHECKSTACK_SYSTEM_ID).toBe("system-9");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("leaves env unchanged when no runContext is supplied (back-compat)", async () => {
|
|
152
|
+
const collector = new ExecuteCollector();
|
|
153
|
+
const client = createMockClient();
|
|
154
|
+
|
|
155
|
+
await collector.execute({
|
|
156
|
+
config: { script: "echo hi", env: { MY_VAR: "value" }, timeout: 3000 },
|
|
157
|
+
client,
|
|
158
|
+
pluginId: "test",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const call = (client.exec as ReturnType<typeof mock>).mock.calls[0][0];
|
|
162
|
+
expect(call.env).toEqual({ MY_VAR: "value" });
|
|
163
|
+
});
|
|
104
164
|
});
|
|
105
165
|
|
|
106
166
|
describe("migration", () => {
|
package/src/execute-collector.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
aggregatedAverage,
|
|
12
12
|
aggregatedRate,
|
|
13
13
|
type InferAggregatedResult,
|
|
14
|
+
type CollectorRunContext,
|
|
14
15
|
} from "@checkstack/backend-api";
|
|
15
16
|
import {
|
|
16
17
|
healthResultNumber,
|
|
@@ -21,6 +22,35 @@ import {
|
|
|
21
22
|
import { pluginMetadata } from "./plugin-metadata";
|
|
22
23
|
import type { ScriptTransportClient } from "./transport-client";
|
|
23
24
|
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// RUN-CONTEXT ENV INJECTION
|
|
27
|
+
// ============================================================================
|
|
28
|
+
//
|
|
29
|
+
// Reserved env var names exposing curated run-context metadata to the
|
|
30
|
+
// shell script. These mirror the automation `CHECKSTACK_` shell
|
|
31
|
+
// convention; intentionally NOT imported from automation-common to keep
|
|
32
|
+
// this plugin's dependency surface minimal.
|
|
33
|
+
|
|
34
|
+
const CHECKSTACK_CHECK_ID = "CHECKSTACK_CHECK_ID";
|
|
35
|
+
const CHECKSTACK_CHECK_NAME = "CHECKSTACK_CHECK_NAME";
|
|
36
|
+
const CHECKSTACK_CHECK_INTERVAL_SECONDS = "CHECKSTACK_CHECK_INTERVAL_SECONDS";
|
|
37
|
+
const CHECKSTACK_SYSTEM_ID = "CHECKSTACK_SYSTEM_ID";
|
|
38
|
+
const CHECKSTACK_SYSTEM_NAME = "CHECKSTACK_SYSTEM_NAME";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Map curated run-context metadata to the reserved `CHECKSTACK_*` env
|
|
42
|
+
* vars exposed to the shell script.
|
|
43
|
+
*/
|
|
44
|
+
function runContextEnv(ctx: CollectorRunContext): Record<string, string> {
|
|
45
|
+
return {
|
|
46
|
+
[CHECKSTACK_CHECK_ID]: ctx.check.id,
|
|
47
|
+
[CHECKSTACK_CHECK_NAME]: ctx.check.name,
|
|
48
|
+
[CHECKSTACK_CHECK_INTERVAL_SECONDS]: String(ctx.check.intervalSeconds),
|
|
49
|
+
[CHECKSTACK_SYSTEM_ID]: ctx.system.id,
|
|
50
|
+
[CHECKSTACK_SYSTEM_NAME]: ctx.system.name,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
24
54
|
// ============================================================================
|
|
25
55
|
// LEGACY CONFIG (v1) — kept for migration
|
|
26
56
|
// ============================================================================
|
|
@@ -212,17 +242,26 @@ export class ExecuteCollector implements CollectorStrategy<
|
|
|
212
242
|
async execute({
|
|
213
243
|
config,
|
|
214
244
|
client,
|
|
245
|
+
runContext,
|
|
215
246
|
}: {
|
|
216
247
|
config: ExecuteConfig;
|
|
217
248
|
client: ScriptTransportClient;
|
|
218
249
|
pluginId: string;
|
|
250
|
+
runContext?: CollectorRunContext;
|
|
219
251
|
}): Promise<CollectorResult<ExecuteResult>> {
|
|
220
252
|
const startTime = Date.now();
|
|
221
253
|
|
|
254
|
+
// Merge run-context metadata under the user-supplied env so a user
|
|
255
|
+
// `config.env` key always wins on collision (matches the runner's
|
|
256
|
+
// existing merge order against the safe-vars whitelist).
|
|
257
|
+
const env = runContext
|
|
258
|
+
? { ...runContextEnv(runContext), ...config.env }
|
|
259
|
+
: config.env;
|
|
260
|
+
|
|
222
261
|
const response = await client.exec({
|
|
223
262
|
script: config.script,
|
|
224
263
|
cwd: config.cwd,
|
|
225
|
-
env
|
|
264
|
+
env,
|
|
226
265
|
timeout: config.timeout,
|
|
227
266
|
});
|
|
228
267
|
|
|
@@ -340,6 +340,49 @@ describe("InlineScriptCollector", () => {
|
|
|
340
340
|
});
|
|
341
341
|
});
|
|
342
342
|
|
|
343
|
+
describe("context.runContext", () => {
|
|
344
|
+
it("exposes run-context metadata as `context.check` / `context.system`", async () => {
|
|
345
|
+
const config = createConfig({
|
|
346
|
+
script: `
|
|
347
|
+
// @ts-ignore — context is injected by the runner
|
|
348
|
+
export default { success: true, message: \`\${context.system.name}:\${context.check.id}\` };
|
|
349
|
+
`,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const result = await collector.execute({
|
|
353
|
+
config,
|
|
354
|
+
client: mockClient,
|
|
355
|
+
pluginId: "script",
|
|
356
|
+
runContext: {
|
|
357
|
+
check: { id: "check-7", name: "CPU load", intervalSeconds: 30 },
|
|
358
|
+
system: { id: "system-3", name: "web-02" },
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(result.result.success).toBe(true);
|
|
363
|
+
expect(result.result.message).toBe("web-02:check-7");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("still exposes `context.config` and does not crash when runContext is absent (back-compat)", async () => {
|
|
367
|
+
const config = createConfig({
|
|
368
|
+
script: `
|
|
369
|
+
// @ts-ignore — context is injected by the runner
|
|
370
|
+
export default { success: true, value: context.config.timeout, message: typeof context.system };
|
|
371
|
+
`,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const result = await collector.execute({
|
|
375
|
+
config,
|
|
376
|
+
client: mockClient,
|
|
377
|
+
pluginId: "script",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(result.result.success).toBe(true);
|
|
381
|
+
expect(result.result.value).toBe(10_000);
|
|
382
|
+
expect(result.result.message).toBe("undefined");
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
343
386
|
describe("mergeResult", () => {
|
|
344
387
|
it("aggregates execution time and success rate", () => {
|
|
345
388
|
const run1 = {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
aggregatedAverage,
|
|
15
15
|
aggregatedRate,
|
|
16
16
|
type InferAggregatedResult,
|
|
17
|
+
type CollectorRunContext,
|
|
17
18
|
} from "@checkstack/backend-api";
|
|
18
19
|
import {
|
|
19
20
|
healthResultNumber,
|
|
@@ -56,20 +57,26 @@ export interface InlineScriptExecutor {
|
|
|
56
57
|
script: string;
|
|
57
58
|
config: Record<string, unknown>;
|
|
58
59
|
timeoutMs: number;
|
|
60
|
+
runContext?: CollectorRunContext;
|
|
59
61
|
}): Promise<InlineScriptExecutionResult>;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* Default executor — delegates to the shared `EsmScriptRunner`. Wires
|
|
64
|
-
* `globalThis.context = { config }` (the inline
|
|
65
|
-
* surface) and the virtual `@checkstack/healthcheck`
|
|
66
|
-
* `defineHealthCheck` helper.
|
|
66
|
+
* `globalThis.context = { config, check?, system? }` (the inline
|
|
67
|
+
* health-check runtime surface) and the virtual `@checkstack/healthcheck`
|
|
68
|
+
* module / global `defineHealthCheck` helper.
|
|
67
69
|
*/
|
|
68
70
|
const defaultInlineScriptExecutor: InlineScriptExecutor = {
|
|
69
|
-
async execute({ script, config, timeoutMs }) {
|
|
71
|
+
async execute({ script, config, timeoutMs, runContext }) {
|
|
70
72
|
const res: EsmScriptRunResult = await defaultEsmScriptRunner.run({
|
|
71
73
|
script,
|
|
72
|
-
context: {
|
|
74
|
+
context: {
|
|
75
|
+
config,
|
|
76
|
+
...(runContext
|
|
77
|
+
? { check: runContext.check, system: runContext.system }
|
|
78
|
+
: {}),
|
|
79
|
+
},
|
|
73
80
|
timeoutMs,
|
|
74
81
|
helperModuleName: "@checkstack/healthcheck",
|
|
75
82
|
helperFunctionName: "defineHealthCheck",
|
|
@@ -254,10 +261,12 @@ export class InlineScriptCollector implements CollectorStrategy<
|
|
|
254
261
|
|
|
255
262
|
async execute({
|
|
256
263
|
config,
|
|
264
|
+
runContext,
|
|
257
265
|
}: {
|
|
258
266
|
config: InlineScriptConfig;
|
|
259
267
|
client: ScriptTransportClient;
|
|
260
268
|
pluginId: string;
|
|
269
|
+
runContext?: CollectorRunContext;
|
|
261
270
|
}): Promise<CollectorResult<InlineScriptResult>> {
|
|
262
271
|
const startTime = Date.now();
|
|
263
272
|
|
|
@@ -267,6 +276,7 @@ export class InlineScriptCollector implements CollectorStrategy<
|
|
|
267
276
|
script: config.script,
|
|
268
277
|
config: config as unknown as Record<string, unknown>,
|
|
269
278
|
timeoutMs: config.timeout,
|
|
279
|
+
runContext,
|
|
270
280
|
});
|
|
271
281
|
} catch (error) {
|
|
272
282
|
const executionTimeMs = Date.now() - startTime;
|