@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.4.1",
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.16.0",
18
- "@checkstack/common": "0.10.0",
19
- "@checkstack/healthcheck-common": "1.1.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.2"
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", () => {
@@ -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: config.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-health-check runtime
65
- * surface) and the virtual `@checkstack/healthcheck` module / global
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: { config },
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;