@griffin-app/griffin-cli 1.0.32 → 1.0.34
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/dist/cli.js +16 -3
- package/dist/commands/hub/apply.d.ts +1 -0
- package/dist/commands/hub/apply.js +23 -2
- package/dist/commands/hub/monitor.d.ts +1 -0
- package/dist/commands/hub/monitor.js +19 -3
- package/dist/commands/local/run.d.ts +9 -0
- package/dist/commands/local/run.js +92 -12
- package/dist/commands/validate.d.ts +1 -0
- package/dist/commands/validate.js +15 -4
- package/dist/core/apply.js +0 -1
- package/dist/core/monitor-helpers.d.ts +26 -1
- package/dist/core/monitor-helpers.js +36 -6
- package/dist/monitor-runner.d.ts +5 -0
- package/dist/monitor-runner.js +47 -8
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -38,8 +38,12 @@ program
|
|
|
38
38
|
program
|
|
39
39
|
.command("validate")
|
|
40
40
|
.description("Check monitor files for errors")
|
|
41
|
-
.
|
|
42
|
-
|
|
41
|
+
.option("--monitor <name>", "Validate only this monitor by name")
|
|
42
|
+
.action(async (options) => {
|
|
43
|
+
await executeValidate({
|
|
44
|
+
monitor: options.monitor,
|
|
45
|
+
json: program.opts().json,
|
|
46
|
+
});
|
|
43
47
|
});
|
|
44
48
|
program
|
|
45
49
|
.command("status")
|
|
@@ -51,18 +55,25 @@ program
|
|
|
51
55
|
.command("test")
|
|
52
56
|
.description("Run monitors locally")
|
|
53
57
|
.option("--env <name>", "Environment name", "default")
|
|
58
|
+
.option("--monitor <name>", "Run only this monitor by name")
|
|
54
59
|
.action(async (options) => {
|
|
55
|
-
await executeRunLocal({
|
|
60
|
+
await executeRunLocal({
|
|
61
|
+
env: options.env,
|
|
62
|
+
monitor: options.monitor,
|
|
63
|
+
json: program.opts().json,
|
|
64
|
+
});
|
|
56
65
|
});
|
|
57
66
|
program
|
|
58
67
|
.command("plan")
|
|
59
68
|
.description("Preview pending monitor changes")
|
|
60
69
|
.option("--env <name>", "Environment name", "default")
|
|
70
|
+
.option("--monitor <name>", "Plan only this monitor by name")
|
|
61
71
|
.option("--json", "Output in JSON format")
|
|
62
72
|
.action(async (options) => {
|
|
63
73
|
await executePlan({
|
|
64
74
|
...options,
|
|
65
75
|
env: options.env,
|
|
76
|
+
monitor: options.monitor,
|
|
66
77
|
json: program.opts().json ?? options.json,
|
|
67
78
|
});
|
|
68
79
|
});
|
|
@@ -70,6 +81,7 @@ program
|
|
|
70
81
|
.command("apply")
|
|
71
82
|
.description("Push monitor changes to the platform")
|
|
72
83
|
.option("--env <name>", "Environment name", "default")
|
|
84
|
+
.option("--monitor <name>", "Apply only this monitor by name")
|
|
73
85
|
.option("--auto-approve", "Skip confirmation prompt")
|
|
74
86
|
.option("--dry-run", "Show what would be done without making changes")
|
|
75
87
|
.option("--prune", "Delete monitors on hub that don't exist locally")
|
|
@@ -77,6 +89,7 @@ program
|
|
|
77
89
|
await executeApply({
|
|
78
90
|
...options,
|
|
79
91
|
env: options.env,
|
|
92
|
+
monitor: options.monitor,
|
|
80
93
|
json: program.opts().json,
|
|
81
94
|
});
|
|
82
95
|
});
|
|
@@ -3,7 +3,8 @@ import { computeDiff, formatDiff } from "../../core/diff.js";
|
|
|
3
3
|
import { applyDiff, formatApplyResult } from "../../core/apply.js";
|
|
4
4
|
import { createSdkAndState } from "../../core/sdk.js";
|
|
5
5
|
import { createCommandHandler } from "../../utils/command-wrapper.js";
|
|
6
|
-
import { discoverLocalMonitors, fetchRemoteMonitors, resolveLocalMonitors, } from "../../core/monitor-helpers.js";
|
|
6
|
+
import { discoverLocalMonitors, fetchRemoteMonitors, resolveLocalMonitors, monitorNotFoundData, } from "../../core/monitor-helpers.js";
|
|
7
|
+
import { OutputError } from "../../utils/output.js";
|
|
7
8
|
/**
|
|
8
9
|
* Apply changes to the hub
|
|
9
10
|
*/
|
|
@@ -15,9 +16,29 @@ export const executeApply = createCommandHandler("apply", async (options, output
|
|
|
15
16
|
const { monitors } = await discoverLocalMonitors(state);
|
|
16
17
|
const remoteMonitors = await fetchRemoteMonitors(sdk, state.projectId, envName);
|
|
17
18
|
const resolvedMonitors = await resolveLocalMonitors(monitors, state.projectId, envName);
|
|
18
|
-
|
|
19
|
+
let diff = computeDiff(resolvedMonitors, remoteMonitors, {
|
|
19
20
|
includeDeletions: options.prune || false,
|
|
20
21
|
});
|
|
22
|
+
if (options.monitor) {
|
|
23
|
+
const name = options.monitor;
|
|
24
|
+
const filtered = diff.actions.filter((a) => a.monitor?.name === name || a.remoteMonitor?.name === name);
|
|
25
|
+
if (filtered.length === 0) {
|
|
26
|
+
const inPlan = diff.actions.map((a) => a.monitor?.name ?? a.remoteMonitor?.name ?? "").filter(Boolean);
|
|
27
|
+
const data = monitorNotFoundData(name, [...new Set(inPlan)]);
|
|
28
|
+
throw new OutputError("NOT_FOUND", `Monitor "${name}" not found in plan`, { available: data.available }, data.available.length
|
|
29
|
+
? "Available: " + data.available.join(", ")
|
|
30
|
+
: undefined);
|
|
31
|
+
}
|
|
32
|
+
diff = {
|
|
33
|
+
actions: filtered,
|
|
34
|
+
summary: {
|
|
35
|
+
creates: filtered.filter((a) => a.type === "create").length,
|
|
36
|
+
updates: filtered.filter((a) => a.type === "update").length,
|
|
37
|
+
deletes: filtered.filter((a) => a.type === "delete").length,
|
|
38
|
+
noops: filtered.filter((a) => a.type === "noop").length,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
21
42
|
output.blank();
|
|
22
43
|
output.log(formatDiff(diff));
|
|
23
44
|
output.blank();
|
|
@@ -2,7 +2,8 @@ import { resolveEnvironment } from "../../core/state.js";
|
|
|
2
2
|
import { createSdkAndState } from "../../core/sdk.js";
|
|
3
3
|
import { computeDiff, formatDiff, formatDiffJson } from "../../core/diff.js";
|
|
4
4
|
import { createCommandHandler } from "../../utils/command-wrapper.js";
|
|
5
|
-
import { discoverLocalMonitors, fetchRemoteMonitors, resolveLocalMonitors, } from "../../core/monitor-helpers.js";
|
|
5
|
+
import { discoverLocalMonitors, fetchRemoteMonitors, resolveLocalMonitors, monitorNotFoundData, } from "../../core/monitor-helpers.js";
|
|
6
|
+
import { OutputError } from "../../utils/output.js";
|
|
6
7
|
/**
|
|
7
8
|
* Show what changes would be applied
|
|
8
9
|
*/
|
|
@@ -11,8 +12,23 @@ export const executePlan = createCommandHandler("plan", async (options, output)
|
|
|
11
12
|
const envName = await resolveEnvironment(options.env);
|
|
12
13
|
const { monitors } = await discoverLocalMonitors(state);
|
|
13
14
|
const remoteMonitors = await fetchRemoteMonitors(sdk, state.projectId, envName);
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
let resolvedMonitors = await resolveLocalMonitors(monitors, state.projectId, envName);
|
|
16
|
+
let remoteFiltered = remoteMonitors;
|
|
17
|
+
if (options.monitor) {
|
|
18
|
+
const name = options.monitor;
|
|
19
|
+
resolvedMonitors = resolvedMonitors.filter((m) => m.name === name);
|
|
20
|
+
remoteFiltered = remoteMonitors.filter((m) => m.name === name);
|
|
21
|
+
if (resolvedMonitors.length === 0 && remoteFiltered.length === 0) {
|
|
22
|
+
const localNames = monitors.map((m) => m.monitor.name);
|
|
23
|
+
const remoteNames = remoteMonitors.map((m) => m.name);
|
|
24
|
+
const available = [...new Set([...localNames, ...remoteNames])];
|
|
25
|
+
const data = monitorNotFoundData(name, available);
|
|
26
|
+
throw new OutputError("NOT_FOUND", `Monitor "${name}" not found locally or on hub`, { available: data.available }, data.available.length
|
|
27
|
+
? "Available: " + data.available.join(", ")
|
|
28
|
+
: undefined);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const diff = computeDiff(resolvedMonitors, remoteFiltered, {
|
|
16
32
|
includeDeletions: false,
|
|
17
33
|
});
|
|
18
34
|
output.setData({
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import type { ExecutionResult } from "@griffin-app/griffin-executor";
|
|
2
|
+
export type TestResultPhase = "load" | "validate" | "resolve" | "execute" | "assert";
|
|
3
|
+
export interface TestResult {
|
|
4
|
+
success: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
phase?: TestResultPhase;
|
|
7
|
+
executionResult?: ExecutionResult;
|
|
8
|
+
}
|
|
1
9
|
export interface RunLocalOptions {
|
|
2
10
|
env: string;
|
|
11
|
+
monitor?: string;
|
|
3
12
|
json?: boolean;
|
|
4
13
|
}
|
|
5
14
|
export declare const executeRunLocal: (options: RunLocalOptions) => Promise<void>;
|
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
import { findTestFiles } from "../../monitor-discovery.js";
|
|
2
|
-
import { runTestFile } from "../../monitor-runner.js";
|
|
2
|
+
import { runTestFile, MonitorRunnerError } from "../../monitor-runner.js";
|
|
3
|
+
import { loadState } from "../../core/state.js";
|
|
3
4
|
import { resolveEnvironment } from "../../core/state.js";
|
|
5
|
+
import { formatDiscoveryErrors } from "../../core/discovery.js";
|
|
6
|
+
import { discoverLocalMonitorsSilent, filterDiscoveredByName, monitorNotFoundData, } from "../../core/monitor-helpers.js";
|
|
7
|
+
import { OutputError } from "../../utils/output.js";
|
|
4
8
|
import { basename } from "path";
|
|
5
9
|
import { createCommandHandler } from "../../utils/command-wrapper.js";
|
|
10
|
+
function inferPhaseFromMessage(message) {
|
|
11
|
+
if (message.startsWith("Invalid monitor") || message.includes("valid TestMonitor"))
|
|
12
|
+
return "validate";
|
|
13
|
+
if (message.startsWith("Failed to load"))
|
|
14
|
+
return "load";
|
|
15
|
+
if (message.startsWith("Error executing monitor"))
|
|
16
|
+
return "execute";
|
|
17
|
+
return "execute";
|
|
18
|
+
}
|
|
6
19
|
export const executeRunLocal = createCommandHandler("test", async (options, output) => {
|
|
7
20
|
const envName = await resolveEnvironment(options.env);
|
|
8
21
|
output.info(`Running tests locally against ${output.colors.cyan(envName)} environment`);
|
|
9
22
|
output.dim(`Variables will be loaded from variables.yaml for environment: ${envName}`);
|
|
10
23
|
output.blank();
|
|
11
24
|
const spinner = output.spinner("Discovering test files...").start();
|
|
12
|
-
|
|
25
|
+
let testFiles;
|
|
26
|
+
if (options.monitor) {
|
|
27
|
+
const state = await loadState();
|
|
28
|
+
const result = await discoverLocalMonitorsSilent(state);
|
|
29
|
+
if (result.errors.length > 0) {
|
|
30
|
+
spinner.fail("Discovery failed");
|
|
31
|
+
output.setData({
|
|
32
|
+
error: "DISCOVERY_FAILED",
|
|
33
|
+
message: formatDiscoveryErrors(result.errors),
|
|
34
|
+
});
|
|
35
|
+
output.error(formatDiscoveryErrors(result.errors));
|
|
36
|
+
output.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const filtered = filterDiscoveredByName(result.monitors, options.monitor);
|
|
39
|
+
if (filtered.length === 0) {
|
|
40
|
+
spinner.fail(`Monitor "${options.monitor}" not found`);
|
|
41
|
+
const data = monitorNotFoundData(options.monitor, result.monitors.map((m) => m.monitor.name));
|
|
42
|
+
throw new OutputError("NOT_FOUND", data.message, { available: data.available }, data.available.length
|
|
43
|
+
? "Available: " + data.available.join(", ")
|
|
44
|
+
: undefined);
|
|
45
|
+
}
|
|
46
|
+
testFiles = [...new Set(filtered.map((m) => m.filePath))];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
testFiles = findTestFiles();
|
|
50
|
+
}
|
|
13
51
|
if (testFiles.length === 0) {
|
|
14
52
|
spinner.fail("No test files found");
|
|
15
53
|
output.setData({
|
|
@@ -22,9 +60,9 @@ export const executeRunLocal = createCommandHandler("test", async (options, outp
|
|
|
22
60
|
output.exit(1);
|
|
23
61
|
}
|
|
24
62
|
spinner.succeed(`Found ${output.colors.bold(testFiles.length.toString())} test file(s)`);
|
|
25
|
-
testFiles.forEach((file) => output.dim(` - ${file}`));
|
|
63
|
+
testFiles.forEach((file) => output.dim(` - ${basename(file)}`));
|
|
26
64
|
output.blank();
|
|
27
|
-
const
|
|
65
|
+
const rawResults = await Promise.all(testFiles.map(async (file) => {
|
|
28
66
|
const fileName = basename(file);
|
|
29
67
|
const testSpinner = output
|
|
30
68
|
.spinner(`Running ${output.colors.cyan(fileName)}`)
|
|
@@ -35,32 +73,74 @@ export const executeRunLocal = createCommandHandler("test", async (options, outp
|
|
|
35
73
|
}
|
|
36
74
|
else {
|
|
37
75
|
testSpinner.fail(`${output.colors.cyan(fileName)} failed`);
|
|
76
|
+
if (result.error) {
|
|
77
|
+
output.dim(` ${result.error}`);
|
|
78
|
+
}
|
|
79
|
+
if (result.executionResult?.errors &&
|
|
80
|
+
result.executionResult.errors.length > 0) {
|
|
81
|
+
for (const err of result.executionResult.errors) {
|
|
82
|
+
output.dim(` • ${err}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const row = {
|
|
87
|
+
file: fileName,
|
|
88
|
+
success: result.success,
|
|
89
|
+
};
|
|
90
|
+
if (!result.success) {
|
|
91
|
+
row.error = result.error;
|
|
92
|
+
row.phase = result.phase;
|
|
93
|
+
if (result.executionResult?.errors &&
|
|
94
|
+
result.executionResult.errors.length > 0) {
|
|
95
|
+
row.errors = result.executionResult.errors;
|
|
96
|
+
}
|
|
38
97
|
}
|
|
39
|
-
return
|
|
98
|
+
return row;
|
|
40
99
|
}));
|
|
41
|
-
const passed =
|
|
42
|
-
const failed =
|
|
100
|
+
const passed = rawResults.filter((r) => r.success).length;
|
|
101
|
+
const failed = rawResults.length - passed;
|
|
43
102
|
output.setData({
|
|
44
|
-
results,
|
|
103
|
+
results: rawResults,
|
|
45
104
|
passed,
|
|
46
105
|
failed,
|
|
47
106
|
});
|
|
48
107
|
output.blank();
|
|
49
108
|
if (failed === 0) {
|
|
50
|
-
output.success(`All tests passed (${output.colors.bold(passed.toString())} / ${
|
|
109
|
+
output.success(`All tests passed (${output.colors.bold(passed.toString())} / ${rawResults.length})`);
|
|
51
110
|
}
|
|
52
111
|
else {
|
|
53
112
|
output.error(`${output.colors.bold(failed.toString())} test(s) failed, ${output.colors.bold(passed.toString())} passed`);
|
|
113
|
+
const failedResults = rawResults.filter((r) => !r.success);
|
|
114
|
+
for (const r of failedResults) {
|
|
115
|
+
output.dim(` • ${r.file}: ${r.error ?? "unknown error"}`);
|
|
116
|
+
}
|
|
54
117
|
output.exit(1);
|
|
55
118
|
}
|
|
56
119
|
});
|
|
57
120
|
async function runTest(file, envName) {
|
|
58
121
|
try {
|
|
59
122
|
const result = await runTestFile(file, envName);
|
|
60
|
-
|
|
123
|
+
if (result.success) {
|
|
124
|
+
return { success: true };
|
|
125
|
+
}
|
|
126
|
+
const error = result.errors?.[0] ?? (result.errors?.length ? result.errors.join("; ") : undefined);
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: error ?? "Monitor run failed",
|
|
130
|
+
phase: "assert",
|
|
131
|
+
executionResult: result,
|
|
132
|
+
};
|
|
61
133
|
}
|
|
62
134
|
catch (error) {
|
|
63
|
-
|
|
64
|
-
|
|
135
|
+
const err = error;
|
|
136
|
+
const message = err?.message ?? String(error);
|
|
137
|
+
const phase = error instanceof MonitorRunnerError
|
|
138
|
+
? error.phase
|
|
139
|
+
: inferPhaseFromMessage(message);
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
error: message,
|
|
143
|
+
phase,
|
|
144
|
+
};
|
|
65
145
|
}
|
|
66
146
|
}
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { loadState } from "../core/state.js";
|
|
2
2
|
import { createCommandHandler } from "../utils/command-wrapper.js";
|
|
3
|
-
import {
|
|
3
|
+
import { OutputError } from "../utils/output.js";
|
|
4
|
+
import { discoverLocalMonitors, filterDiscoveredByName, monitorNotFoundData, } from "../core/monitor-helpers.js";
|
|
4
5
|
/**
|
|
5
6
|
* Validate test monitor files without syncing
|
|
6
7
|
*/
|
|
7
|
-
export const executeValidate = createCommandHandler("validate", async (
|
|
8
|
+
export const executeValidate = createCommandHandler("validate", async (options, output) => {
|
|
8
9
|
const state = await loadState();
|
|
9
10
|
const { monitors } = await discoverLocalMonitors(state);
|
|
11
|
+
let toValidate = monitors;
|
|
12
|
+
if (options.monitor) {
|
|
13
|
+
toValidate = filterDiscoveredByName(monitors, options.monitor);
|
|
14
|
+
if (toValidate.length === 0) {
|
|
15
|
+
const data = monitorNotFoundData(options.monitor, monitors.map((m) => m.monitor.name));
|
|
16
|
+
throw new OutputError("NOT_FOUND", data.message, { available: data.available }, data.available.length
|
|
17
|
+
? "Available: " + data.available.join(", ")
|
|
18
|
+
: undefined);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
10
21
|
output.setData({
|
|
11
22
|
valid: true,
|
|
12
|
-
monitors:
|
|
23
|
+
monitors: toValidate.map(({ monitor, filePath, exportName }) => ({
|
|
13
24
|
name: monitor.name,
|
|
14
25
|
filePath: filePath.replace(process.cwd(), "."),
|
|
15
26
|
exportName,
|
|
@@ -21,7 +32,7 @@ export const executeValidate = createCommandHandler("validate", async (_options,
|
|
|
21
32
|
})),
|
|
22
33
|
});
|
|
23
34
|
output.blank();
|
|
24
|
-
for (const { monitor, filePath, exportName } of
|
|
35
|
+
for (const { monitor, filePath, exportName } of toValidate) {
|
|
25
36
|
const shortPath = filePath.replace(process.cwd(), ".");
|
|
26
37
|
const exportInfo = exportName === "default" ? "" : output.colors.dim(` (${exportName})`);
|
|
27
38
|
output.log(` ${output.colors.green("●")} ${output.colors.cyan(monitor.name)}${exportInfo}`);
|
package/dist/core/apply.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import type { GriffinHubSdk, MonitorV1 } from "@griffin-app/griffin-hub-sdk";
|
|
2
2
|
import type { StateFile } from "../schemas/state.js";
|
|
3
|
-
import { type DiscoveryResult } from "./discovery.js";
|
|
3
|
+
import { type DiscoveredMonitor, type DiscoveryResult } from "./discovery.js";
|
|
4
4
|
import { MonitorDSL } from "@griffin-app/griffin-core/types";
|
|
5
|
+
/**
|
|
6
|
+
* Get discovery pattern and ignore list from state (shared defaults).
|
|
7
|
+
*/
|
|
8
|
+
export declare function getDiscoveryConfig(state?: StateFile): {
|
|
9
|
+
pattern: string;
|
|
10
|
+
ignore: string[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Discover local monitors using state-configured patterns, without spinner or exit.
|
|
14
|
+
* Caller handles errors and UI. Use when you need to discover and then filter or handle errors yourself (e.g. test --monitor).
|
|
15
|
+
*/
|
|
16
|
+
export declare function discoverLocalMonitorsSilent(state?: StateFile): Promise<DiscoveryResult>;
|
|
5
17
|
/**
|
|
6
18
|
* Discover local monitors using state-configured patterns.
|
|
7
19
|
* Shows spinner, handles errors, and returns the discovery result.
|
|
@@ -18,3 +30,16 @@ export declare function fetchRemoteMonitors(sdk: GriffinHubSdk, projectId: strin
|
|
|
18
30
|
export declare function resolveLocalMonitors(monitors: {
|
|
19
31
|
monitor: MonitorDSL;
|
|
20
32
|
}[], projectId: string, envName: string): Promise<Omit<MonitorV1, "id">[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Filter discovered monitors by name (exact match).
|
|
35
|
+
*/
|
|
36
|
+
export declare function filterDiscoveredByName(monitors: DiscoveredMonitor[], name: string): DiscoveredMonitor[];
|
|
37
|
+
/**
|
|
38
|
+
* Build NOT_FOUND error data for JSON output when a monitor name is missing.
|
|
39
|
+
* Use with output.setData() then output.error(), output.info("Available monitors:"), etc., and output.exit(1).
|
|
40
|
+
*/
|
|
41
|
+
export declare function monitorNotFoundData(monitorName: string, availableNames: string[]): {
|
|
42
|
+
error: "NOT_FOUND";
|
|
43
|
+
message: string;
|
|
44
|
+
available: string[];
|
|
45
|
+
};
|
|
@@ -3,18 +3,31 @@ import { terminal } from "../utils/terminal.js";
|
|
|
3
3
|
import { withSDKErrorHandling } from "../utils/sdk-error.js";
|
|
4
4
|
import { loadVariables } from "./variables.js";
|
|
5
5
|
import { resolveMonitor } from "../resolve.js";
|
|
6
|
+
/**
|
|
7
|
+
* Get discovery pattern and ignore list from state (shared defaults).
|
|
8
|
+
*/
|
|
9
|
+
export function getDiscoveryConfig(state) {
|
|
10
|
+
return {
|
|
11
|
+
pattern: state?.discovery?.pattern ?? "**/__griffin__/*.{ts,js}",
|
|
12
|
+
ignore: state?.discovery?.ignore ?? ["node_modules/**", "dist/**"],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Discover local monitors using state-configured patterns, without spinner or exit.
|
|
17
|
+
* Caller handles errors and UI. Use when you need to discover and then filter or handle errors yourself (e.g. test --monitor).
|
|
18
|
+
*/
|
|
19
|
+
export async function discoverLocalMonitorsSilent(state) {
|
|
20
|
+
const { pattern, ignore } = getDiscoveryConfig(state);
|
|
21
|
+
return discoverMonitors(pattern, ignore);
|
|
22
|
+
}
|
|
6
23
|
/**
|
|
7
24
|
* Discover local monitors using state-configured patterns.
|
|
8
25
|
* Shows spinner, handles errors, and returns the discovery result.
|
|
9
26
|
*/
|
|
10
27
|
export async function discoverLocalMonitors(state) {
|
|
11
|
-
const
|
|
12
|
-
const discoveryIgnore = state?.discovery?.ignore || [
|
|
13
|
-
"node_modules/**",
|
|
14
|
-
"dist/**",
|
|
15
|
-
];
|
|
28
|
+
const { pattern, ignore } = getDiscoveryConfig(state);
|
|
16
29
|
const spinner = terminal.spinner("Discovering local monitors...").start();
|
|
17
|
-
const result = await discoverMonitors(
|
|
30
|
+
const result = await discoverMonitors(pattern, ignore);
|
|
18
31
|
if (result.errors.length > 0) {
|
|
19
32
|
spinner.fail("Discovery failed");
|
|
20
33
|
terminal.error(formatDiscoveryErrors(result.errors));
|
|
@@ -46,3 +59,20 @@ export async function resolveLocalMonitors(monitors, projectId, envName) {
|
|
|
46
59
|
const variables = await loadVariables(envName);
|
|
47
60
|
return monitors.map((p) => resolveMonitor(p.monitor, projectId, envName, variables));
|
|
48
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Filter discovered monitors by name (exact match).
|
|
64
|
+
*/
|
|
65
|
+
export function filterDiscoveredByName(monitors, name) {
|
|
66
|
+
return monitors.filter((m) => m.monitor.name === name);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build NOT_FOUND error data for JSON output when a monitor name is missing.
|
|
70
|
+
* Use with output.setData() then output.error(), output.info("Available monitors:"), etc., and output.exit(1).
|
|
71
|
+
*/
|
|
72
|
+
export function monitorNotFoundData(monitorName, availableNames) {
|
|
73
|
+
return {
|
|
74
|
+
error: "NOT_FOUND",
|
|
75
|
+
message: `Monitor "${monitorName}" not found`,
|
|
76
|
+
available: availableNames,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/dist/monitor-runner.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import "tsx";
|
|
2
2
|
import { ExecutionResult } from "@griffin-app/griffin-executor";
|
|
3
|
+
export type MonitorRunnerPhase = "load" | "validate" | "resolve" | "execute" | "assert";
|
|
4
|
+
export declare class MonitorRunnerError extends Error {
|
|
5
|
+
readonly phase: MonitorRunnerPhase;
|
|
6
|
+
constructor(phase: MonitorRunnerPhase, message: string, cause?: unknown);
|
|
7
|
+
}
|
|
3
8
|
/**
|
|
4
9
|
* Runs a TypeScript test file and executes the resulting JSON monitor.
|
|
5
10
|
*/
|
package/dist/monitor-runner.js
CHANGED
|
@@ -6,9 +6,20 @@ import { randomUUID } from "crypto";
|
|
|
6
6
|
import { loadVariables } from "./core/variables.js";
|
|
7
7
|
import { getProjectId } from "./core/state.js";
|
|
8
8
|
import { resolveMonitor } from "./resolve.js";
|
|
9
|
-
import { terminal } from "./utils/terminal.js";
|
|
10
9
|
import { createEnvSecretsProvider, HubSecretProvider } from "./core/secrets.js";
|
|
11
10
|
import { createSdkFromState } from "./core/sdk.js";
|
|
11
|
+
export class MonitorRunnerError extends Error {
|
|
12
|
+
phase;
|
|
13
|
+
constructor(phase, message, cause) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.phase = phase;
|
|
16
|
+
this.name = "MonitorRunnerError";
|
|
17
|
+
if (cause !== undefined && cause instanceof Error && cause.cause === undefined) {
|
|
18
|
+
this.cause = cause;
|
|
19
|
+
}
|
|
20
|
+
Object.setPrototypeOf(this, MonitorRunnerError.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
12
23
|
function validateDsl(monitor) {
|
|
13
24
|
const errors = Value.Errors(MonitorDSLSchema, monitor);
|
|
14
25
|
if (errors.length > 0) {
|
|
@@ -16,16 +27,43 @@ function validateDsl(monitor) {
|
|
|
16
27
|
}
|
|
17
28
|
return monitor;
|
|
18
29
|
}
|
|
30
|
+
function messageFromCause(cause) {
|
|
31
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
32
|
+
}
|
|
19
33
|
/**
|
|
20
34
|
* Runs a TypeScript test file and executes the resulting JSON monitor.
|
|
21
35
|
*/
|
|
22
36
|
export async function runTestFile(filePath, envName) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
let variables;
|
|
38
|
+
let projectId;
|
|
39
|
+
try {
|
|
40
|
+
variables = await loadVariables(envName);
|
|
41
|
+
projectId = await getProjectId();
|
|
42
|
+
}
|
|
43
|
+
catch (cause) {
|
|
44
|
+
throw new MonitorRunnerError("resolve", messageFromCause(cause), cause);
|
|
45
|
+
}
|
|
46
|
+
let defaultExport;
|
|
47
|
+
try {
|
|
48
|
+
defaultExport = await import(filePath);
|
|
49
|
+
}
|
|
50
|
+
catch (cause) {
|
|
51
|
+
throw new MonitorRunnerError("load", messageFromCause(cause), cause);
|
|
52
|
+
}
|
|
53
|
+
let rawMonitor;
|
|
54
|
+
try {
|
|
55
|
+
rawMonitor = validateDsl(defaultExport.default);
|
|
56
|
+
}
|
|
57
|
+
catch (cause) {
|
|
58
|
+
throw new MonitorRunnerError("validate", messageFromCause(cause), cause);
|
|
59
|
+
}
|
|
60
|
+
let resolvedMonitor;
|
|
61
|
+
try {
|
|
62
|
+
resolvedMonitor = resolveMonitor(rawMonitor, projectId, envName, variables);
|
|
63
|
+
}
|
|
64
|
+
catch (cause) {
|
|
65
|
+
throw new MonitorRunnerError("resolve", messageFromCause(cause), cause);
|
|
66
|
+
}
|
|
29
67
|
// Create secret provider: prefer hub (authenticated) for cloud secrets,
|
|
30
68
|
// fall back to env provider for fully-local runs.
|
|
31
69
|
const monitorV1 = { ...resolvedMonitor, id: randomUUID() };
|
|
@@ -46,6 +84,7 @@ export async function runTestFile(filePath, envName) {
|
|
|
46
84
|
return result;
|
|
47
85
|
}
|
|
48
86
|
catch (error) {
|
|
49
|
-
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
throw new MonitorRunnerError("execute", `Error executing monitor: ${message}`, error);
|
|
50
89
|
}
|
|
51
90
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griffin-app/griffin-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"description": "CLI tool for running and managing griffin API tests",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"author": "",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@griffin-app/griffin-core": "0.2.
|
|
28
|
-
"@griffin-app/griffin-executor": "0.1.
|
|
29
|
-
"@griffin-app/griffin-hub-sdk": "1.0.
|
|
27
|
+
"@griffin-app/griffin-core": "0.2.6",
|
|
28
|
+
"@griffin-app/griffin-executor": "0.1.8",
|
|
29
|
+
"@griffin-app/griffin-hub-sdk": "1.0.30",
|
|
30
30
|
"better-auth": "^1.4.17",
|
|
31
31
|
"cli-table3": "^0.6.5",
|
|
32
32
|
"commander": "^12.1.0",
|