@griffin-app/griffin-cli 1.0.39 → 1.0.40
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 +0 -0
- package/dist/commands/apply.d.ts +9 -0
- package/dist/commands/apply.js +76 -0
- package/dist/commands/config.d.ts +36 -0
- package/dist/commands/config.js +144 -0
- package/dist/commands/configure-runner-host.d.ts +2 -0
- package/dist/commands/configure-runner-host.js +41 -0
- package/dist/commands/deploy.d.ts +1 -0
- package/dist/commands/deploy.js +36 -0
- package/dist/commands/execute-remote.d.ts +1 -0
- package/dist/commands/execute-remote.js +33 -0
- package/dist/commands/hub/config.d.ts +27 -0
- package/dist/commands/hub/config.js +102 -0
- package/dist/commands/hub/plan.d.ts +8 -0
- package/dist/commands/hub/plan.js +75 -0
- package/dist/commands/local/config.d.ts +28 -0
- package/dist/commands/local/config.js +82 -0
- package/dist/commands/logs.d.ts +1 -0
- package/dist/commands/logs.js +20 -0
- package/dist/commands/plan.d.ts +8 -0
- package/dist/commands/plan.js +58 -0
- package/dist/commands/run-remote.d.ts +11 -0
- package/dist/commands/run-remote.js +98 -0
- package/dist/commands/run.d.ts +4 -0
- package/dist/commands/run.js +86 -0
- package/dist/commands/runner.d.ts +12 -0
- package/dist/commands/runner.js +53 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +75 -0
- package/dist/core/plan-diff.d.ts +41 -0
- package/dist/core/plan-diff.js +257 -0
- package/dist/output/context.d.ts +18 -0
- package/dist/output/context.js +22 -0
- package/dist/output/index.d.ts +3 -0
- package/dist/output/index.js +2 -0
- package/dist/output/renderer.d.ts +6 -0
- package/dist/output/renderer.js +348 -0
- package/dist/output/types.d.ts +153 -0
- package/dist/output/types.js +1 -0
- package/dist/providers/registry.d.ts +24 -0
- package/dist/providers/registry.js +109 -0
- package/dist/schemas/payload.d.ts +6 -0
- package/dist/schemas/payload.js +8 -0
- package/dist/test-discovery.d.ts +4 -0
- package/dist/test-discovery.js +25 -0
- package/dist/test-runner.d.ts +6 -0
- package/dist/test-runner.js +56 -0
- package/dist/utils/console.d.ts +5 -0
- package/dist/utils/console.js +5 -0
- package/package.json +3 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { addTarget, removeTarget, listEnvironments, setDefaultEnvironment, } from "../../core/state.js";
|
|
2
|
+
/**
|
|
3
|
+
* List all local environments and their targets
|
|
4
|
+
*/
|
|
5
|
+
export async function executeConfigList() {
|
|
6
|
+
try {
|
|
7
|
+
const environments = await listEnvironments();
|
|
8
|
+
const envNames = Object.keys(environments);
|
|
9
|
+
if (envNames.length === 0) {
|
|
10
|
+
console.log("No environments configured.");
|
|
11
|
+
console.log("");
|
|
12
|
+
console.log("Add a target to create an environment:");
|
|
13
|
+
console.log(" griffin local config add-target --env <name> --key <key> --url <url>");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
console.log("Local environments:");
|
|
17
|
+
console.log("");
|
|
18
|
+
for (const envName of envNames) {
|
|
19
|
+
const env = environments[envName];
|
|
20
|
+
const marker = env.isDefault ? " (default)" : "";
|
|
21
|
+
console.log(` ${envName}${marker}`);
|
|
22
|
+
const targetKeys = Object.keys(env.targets);
|
|
23
|
+
if (targetKeys.length === 0) {
|
|
24
|
+
console.log(" (no targets)");
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
for (const [key, url] of Object.entries(env.targets)) {
|
|
28
|
+
console.log(` ${key}: ${url}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
console.log("");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error(`Error: ${error.message}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Add a target to a local environment
|
|
41
|
+
*/
|
|
42
|
+
export async function executeConfigAddTarget(options) {
|
|
43
|
+
try {
|
|
44
|
+
await addTarget(options.env, options.key, options.url);
|
|
45
|
+
console.log(`✓ Target added to '${options.env}' environment`);
|
|
46
|
+
console.log(` ${options.key}: ${options.url}`);
|
|
47
|
+
console.log("");
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error(`Error: ${error.message}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Remove a target from a local environment
|
|
56
|
+
*/
|
|
57
|
+
export async function executeConfigRemoveTarget(options) {
|
|
58
|
+
try {
|
|
59
|
+
await removeTarget(options.env, options.key);
|
|
60
|
+
console.log(`✓ Target removed from '${options.env}' environment`);
|
|
61
|
+
console.log(` Key: ${options.key}`);
|
|
62
|
+
console.log("");
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(`Error: ${error.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Set the default environment
|
|
71
|
+
*/
|
|
72
|
+
export async function executeConfigSetDefaultEnv(options) {
|
|
73
|
+
try {
|
|
74
|
+
await setDefaultEnvironment(options.env);
|
|
75
|
+
console.log(`✓ Set '${options.env}' as default environment`);
|
|
76
|
+
console.log("");
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error(`Error: ${error.message}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function executeLogs(checkName: string): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getRunnerHost } from "./configure-runner-host";
|
|
2
|
+
const axios = require("axios");
|
|
3
|
+
export async function executeLogs(checkName) {
|
|
4
|
+
const runnerHost = getRunnerHost();
|
|
5
|
+
if (!runnerHost) {
|
|
6
|
+
console.error("ERROR: No runner host configured. Run: griffin configure-runner-host <host>");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
console.log(`Fetching logs for: ${checkName}`);
|
|
10
|
+
try {
|
|
11
|
+
// TODO: Implement log fetching from runner API
|
|
12
|
+
const response = await axios.get(`${runnerHost}/api/tests/${checkName}/logs`);
|
|
13
|
+
console.log(JSON.stringify(response.data, null, 2));
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error("ERROR: Failed to fetch logs");
|
|
17
|
+
console.error(error.message || String(error));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { loadState, resolveEnvironment } from "../core/state.js";
|
|
2
|
+
import { discoverMonitors, formatDiscoveryErrors } from "../core/discovery.js";
|
|
3
|
+
import { createSdkClients } from "../core/sdk.js";
|
|
4
|
+
import { computeDiff, formatDiff, formatDiffJson } from "../core/diff.js";
|
|
5
|
+
/**
|
|
6
|
+
* Show what changes would be applied
|
|
7
|
+
*/
|
|
8
|
+
export async function executeMonitor(options) {
|
|
9
|
+
try {
|
|
10
|
+
// Load state
|
|
11
|
+
const state = await loadState();
|
|
12
|
+
// Resolve environment
|
|
13
|
+
const envName = await resolveEnvironment(options.env);
|
|
14
|
+
if (!state.runner?.baseUrl) {
|
|
15
|
+
console.error("Error: Runner URL not configured.");
|
|
16
|
+
console.log("Configure with:");
|
|
17
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
// Discover local monitors
|
|
21
|
+
const discoveryPattern = state.discovery?.pattern || "**/__griffin__/*.{ts,js}";
|
|
22
|
+
const discoveryIgnore = state.discovery?.ignore || [
|
|
23
|
+
"node_modules/**",
|
|
24
|
+
"dist/**",
|
|
25
|
+
];
|
|
26
|
+
const { monitors, errors } = await discoverMonitors(discoveryPattern, discoveryIgnore);
|
|
27
|
+
if (errors.length > 0) {
|
|
28
|
+
console.error(formatDiscoveryErrors(errors));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// Create SDK clients
|
|
32
|
+
const { planApi } = createSdkClients({
|
|
33
|
+
baseUrl: state.runner.baseUrl,
|
|
34
|
+
apiToken: state.runner.apiToken || undefined,
|
|
35
|
+
});
|
|
36
|
+
// Fetch remote monitors for this project
|
|
37
|
+
const response = await planApi.planGet(state.projectId);
|
|
38
|
+
const remoteMonitors = response.data.data.map((p) => p);
|
|
39
|
+
// Compute diff for this environment
|
|
40
|
+
const diff = computeDiff(monitors.map((p) => p.monitor), state, remoteMonitors, envName);
|
|
41
|
+
// Output
|
|
42
|
+
if (options.json) {
|
|
43
|
+
console.log(formatDiffJson(diff));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log(formatDiff(diff));
|
|
47
|
+
}
|
|
48
|
+
// Exit with error code if there are changes
|
|
49
|
+
if (diff.summary.creates + diff.summary.updates + diff.summary.deletes >
|
|
50
|
+
0) {
|
|
51
|
+
process.exit(2); // Exit code 2 indicates changes pending
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error(`Error: ${error.message}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { loadState, resolveEnvironment } from "../core/state.js";
|
|
2
|
+
import { createSdkClients } from "../core/sdk.js";
|
|
3
|
+
/**
|
|
4
|
+
* Trigger a monitor run on the runner
|
|
5
|
+
*/
|
|
6
|
+
export async function executeRun(options) {
|
|
7
|
+
try {
|
|
8
|
+
// TODO: remove this. Users shoud be able to execute all monitors if they want.
|
|
9
|
+
if (!options.planId && !options.planName) {
|
|
10
|
+
console.error("Error: Either --monitor-id or --monitor-name must be provided");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (!options.targetEnv) {
|
|
14
|
+
console.error("Error: --target-env must be provided (e.g., staging, production)");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
// Load state
|
|
18
|
+
const state = await loadState();
|
|
19
|
+
if (!state.runner?.baseUrl) {
|
|
20
|
+
console.error("Error: Runner URL not configured.");
|
|
21
|
+
console.log("Configure with:");
|
|
22
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
// Create SDK clients (for status polling)
|
|
26
|
+
const { runsApi } = createSdkClients({
|
|
27
|
+
baseUrl: state.runner.baseUrl,
|
|
28
|
+
apiToken: state.runner.apiToken || undefined,
|
|
29
|
+
});
|
|
30
|
+
// Resolve monitor ID from name if needed
|
|
31
|
+
let planId = options.planId;
|
|
32
|
+
if (!planId && options.planName) {
|
|
33
|
+
// Resolve environment to search in
|
|
34
|
+
const envName = await resolveEnvironment(options.env);
|
|
35
|
+
const envMonitors = state.monitors[envName] || [];
|
|
36
|
+
const stateEntry = envMonitors.find((p) => p.planName === options.planName);
|
|
37
|
+
if (!stateEntry) {
|
|
38
|
+
console.error(`Error: Monitor "${options.planName}" not found in state`);
|
|
39
|
+
console.error("Run 'griffin apply' to sync your monitors first");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
planId = stateEntry.planId;
|
|
43
|
+
}
|
|
44
|
+
// Trigger the run with environment
|
|
45
|
+
console.log(`Triggering run for monitor: ${planId}`);
|
|
46
|
+
console.log(`Target environment: ${options.targetEnv}`);
|
|
47
|
+
const response = await runsApi.runsTriggerMonitorIdPost(planId, {
|
|
48
|
+
environment: options.targetEnv,
|
|
49
|
+
});
|
|
50
|
+
console.log(`Run ID: ${response.data.data.id}`);
|
|
51
|
+
console.log(`Status: ${response.data.data.status}`);
|
|
52
|
+
console.log(`Started: ${new Date(response.data.data.startedAt).toLocaleString()}`);
|
|
53
|
+
// Wait for completion if requested
|
|
54
|
+
if (options.wait) {
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log("Waiting for run to complete...");
|
|
57
|
+
const runId = response.data.data.id;
|
|
58
|
+
let completed = false;
|
|
59
|
+
while (!completed) {
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 2000)); // Poll every 2 seconds
|
|
61
|
+
const { data: runResponse } = await runsApi.runsIdGet(runId);
|
|
62
|
+
const run = runResponse.data;
|
|
63
|
+
if (run.status === "completed" || run.status === "failed") {
|
|
64
|
+
completed = true;
|
|
65
|
+
console.log("");
|
|
66
|
+
console.log(`✓ Run ${run.status}`);
|
|
67
|
+
if (run.duration_ms) {
|
|
68
|
+
console.log(`Duration: ${(run.duration_ms / 1000).toFixed(2)}s`);
|
|
69
|
+
}
|
|
70
|
+
if (run.success !== undefined) {
|
|
71
|
+
console.log(`Success: ${run.success ? "Yes" : "No"}`);
|
|
72
|
+
}
|
|
73
|
+
if (run.errors && run.errors.length > 0) {
|
|
74
|
+
console.log("");
|
|
75
|
+
console.log("Errors:");
|
|
76
|
+
for (const error of run.errors) {
|
|
77
|
+
console.log(` - ${error}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!run.success) {
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
process.stdout.write(".");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log("Run started. Use 'griffin status' to check progress.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error(`Error: ${error.message}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { findTestFiles } from "../test-discovery.js";
|
|
2
|
+
import { runTestFile } from "../test-runner.js";
|
|
3
|
+
import { resolveEnvironment, getEnvironment } from "../core/state.js";
|
|
4
|
+
import { basename } from "path";
|
|
5
|
+
export async function executeRunLocal(options = {}) {
|
|
6
|
+
try {
|
|
7
|
+
// Resolve environment
|
|
8
|
+
const envName = await resolveEnvironment(options.env);
|
|
9
|
+
const envConfig = await getEnvironment(envName);
|
|
10
|
+
console.log(`Running tests locally against '${envName}' environment`);
|
|
11
|
+
console.log(`Target: ${envConfig.baseUrl}`);
|
|
12
|
+
console.log("");
|
|
13
|
+
const testFiles = findTestFiles();
|
|
14
|
+
if (testFiles.length === 0) {
|
|
15
|
+
console.error("No test files found. Looking for .ts files in __griffin__ directories.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
console.log(`Found ${testFiles.length} test file(s):`);
|
|
19
|
+
testFiles.forEach((file) => console.log(` - ${file}`));
|
|
20
|
+
console.log("");
|
|
21
|
+
const results = await Promise.all(testFiles.map(async (file) => {
|
|
22
|
+
const fileName = basename(file);
|
|
23
|
+
console.log(`Running ${fileName}`);
|
|
24
|
+
const result = await runTest(file, envConfig.baseUrl);
|
|
25
|
+
return result;
|
|
26
|
+
}));
|
|
27
|
+
// Print summary
|
|
28
|
+
const successful = results.filter((r) => r.success).length;
|
|
29
|
+
const failed = results.length - successful;
|
|
30
|
+
console.log("");
|
|
31
|
+
console.log(`Summary: ${successful} passed, ${failed} failed`);
|
|
32
|
+
if (failed > 0) {
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error(`Error: ${error.message}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function runTest(file, baseUrl) {
|
|
42
|
+
try {
|
|
43
|
+
const result = await runTestFile(file, baseUrl);
|
|
44
|
+
console.log("SUCCESS: result", result);
|
|
45
|
+
return { success: result.success };
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error("ERROR: Failed to run test");
|
|
49
|
+
console.error(error.message || String(error));
|
|
50
|
+
return { success: false };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function displayResults(result) {
|
|
54
|
+
if (!result)
|
|
55
|
+
return false;
|
|
56
|
+
const success = result.success || false;
|
|
57
|
+
const nodeResults = result.results || [];
|
|
58
|
+
const errors = result.errors || [];
|
|
59
|
+
nodeResults.forEach((nodeResult) => {
|
|
60
|
+
const nodeId = nodeResult.nodeId || "unknown";
|
|
61
|
+
const nodeSuccess = nodeResult.success || false;
|
|
62
|
+
const status = nodeSuccess ? "." : "E";
|
|
63
|
+
process.stdout.write(status);
|
|
64
|
+
if (!nodeSuccess) {
|
|
65
|
+
const error = nodeResult.error || "Unknown error";
|
|
66
|
+
console.log("");
|
|
67
|
+
console.log(`ERROR in ${nodeId}: ${error}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
console.log("");
|
|
71
|
+
if (errors.length > 0) {
|
|
72
|
+
console.log("Errors:");
|
|
73
|
+
errors.forEach((error) => console.log(` - ${error}`));
|
|
74
|
+
}
|
|
75
|
+
// Check if any node failed or if there are errors
|
|
76
|
+
const anyFailed = nodeResults.some((nodeResult) => !nodeResult.success) ||
|
|
77
|
+
errors.length > 0;
|
|
78
|
+
const testPassed = success && !anyFailed;
|
|
79
|
+
if (testPassed) {
|
|
80
|
+
console.log("✓ Test passed");
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log("✗ Test failed");
|
|
84
|
+
}
|
|
85
|
+
return testPassed;
|
|
86
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RunnerSetOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiToken?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Configure runner connection settings
|
|
7
|
+
*/
|
|
8
|
+
export declare function executeRunnerSet(options: RunnerSetOptions): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Show current runner configuration
|
|
11
|
+
*/
|
|
12
|
+
export declare function executeRunnerShow(): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { loadState, saveState } from "../core/state.js";
|
|
2
|
+
/**
|
|
3
|
+
* Configure runner connection settings
|
|
4
|
+
*/
|
|
5
|
+
export async function executeRunnerSet(options) {
|
|
6
|
+
try {
|
|
7
|
+
const state = await loadState();
|
|
8
|
+
// Update runner config
|
|
9
|
+
state.runner = {
|
|
10
|
+
baseUrl: options.baseUrl,
|
|
11
|
+
apiToken: options.apiToken,
|
|
12
|
+
};
|
|
13
|
+
await saveState(state);
|
|
14
|
+
console.log("✓ Runner configuration updated");
|
|
15
|
+
console.log(` Base URL: ${options.baseUrl}`);
|
|
16
|
+
if (options.apiToken) {
|
|
17
|
+
console.log(" API Token: ***");
|
|
18
|
+
}
|
|
19
|
+
console.log("");
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error(`Error: ${error.message}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Show current runner configuration
|
|
28
|
+
*/
|
|
29
|
+
export async function executeRunnerShow() {
|
|
30
|
+
try {
|
|
31
|
+
const state = await loadState();
|
|
32
|
+
if (!state.runner) {
|
|
33
|
+
console.log("No runner configured.");
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log("Configure with:");
|
|
36
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log("Runner configuration:");
|
|
40
|
+
console.log(` Base URL: ${state.runner.baseUrl}`);
|
|
41
|
+
if (state.runner.apiToken) {
|
|
42
|
+
console.log(` API Token: ${state.runner.apiToken.substring(0, 8)}...`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.log(" API Token: (not set)");
|
|
46
|
+
}
|
|
47
|
+
console.log("");
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error(`Error: ${error.message}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { loadState } from "../core/state.js";
|
|
2
|
+
import { createSdkClients } from "../core/sdk.js";
|
|
3
|
+
/**
|
|
4
|
+
* Show runner status and recent runs
|
|
5
|
+
*/
|
|
6
|
+
export async function executeStatus(options) {
|
|
7
|
+
try {
|
|
8
|
+
// Load state
|
|
9
|
+
const state = await loadState();
|
|
10
|
+
if (!state.runner?.baseUrl) {
|
|
11
|
+
console.error("Error: Runner URL not configured.");
|
|
12
|
+
console.log("Configure with:");
|
|
13
|
+
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
// Create SDK clients
|
|
17
|
+
const { runsApi } = createSdkClients({
|
|
18
|
+
baseUrl: state.runner.baseUrl,
|
|
19
|
+
apiToken: state.runner.apiToken || undefined,
|
|
20
|
+
});
|
|
21
|
+
console.log(`Runner: ${state.runner.baseUrl}`);
|
|
22
|
+
console.log("");
|
|
23
|
+
// Get recent runs
|
|
24
|
+
const limit = options.limit || 10;
|
|
25
|
+
const response = await runsApi.runsGet(options.planId, undefined, limit, 0);
|
|
26
|
+
const runs = response.data;
|
|
27
|
+
if (runs.total === 0) {
|
|
28
|
+
console.log("No runs found.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log(`Recent runs (${runs.total} total):`);
|
|
32
|
+
console.log("");
|
|
33
|
+
for (const run of runs.runs) {
|
|
34
|
+
const statusIcon = getStatusIcon(run.status, run.success);
|
|
35
|
+
const duration = run.duration_ms
|
|
36
|
+
? ` (${(run.duration_ms / 1000).toFixed(2)}s)`
|
|
37
|
+
: "";
|
|
38
|
+
console.log(`${statusIcon} ${run.planName}`);
|
|
39
|
+
console.log(` ID: ${run.id}`);
|
|
40
|
+
console.log(` Status: ${run.status}${duration}`);
|
|
41
|
+
console.log(` Started: ${new Date(run.startedAt).toLocaleString()}`);
|
|
42
|
+
if (run.completedAt) {
|
|
43
|
+
console.log(` Completed: ${new Date(run.completedAt).toLocaleString()}`);
|
|
44
|
+
}
|
|
45
|
+
if (run.errors && run.errors.length > 0) {
|
|
46
|
+
console.log(` Errors:`);
|
|
47
|
+
for (const error of run.errors.slice(0, 3)) {
|
|
48
|
+
console.log(` - ${error}`);
|
|
49
|
+
}
|
|
50
|
+
if (run.errors.length > 3) {
|
|
51
|
+
console.log(` ... and ${run.errors.length - 3} more`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
console.log("");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error(`Error: ${error.message}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getStatusIcon(status, success) {
|
|
63
|
+
switch (status) {
|
|
64
|
+
case "pending":
|
|
65
|
+
return "⏳";
|
|
66
|
+
case "running":
|
|
67
|
+
return "🏃";
|
|
68
|
+
case "completed":
|
|
69
|
+
return success ? "✓" : "✗";
|
|
70
|
+
case "failed":
|
|
71
|
+
return "✗";
|
|
72
|
+
default:
|
|
73
|
+
return "•";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MonitorV1 } from "@griffin-app/griffin-hub-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Represents a change to a single field
|
|
4
|
+
*/
|
|
5
|
+
export interface FieldChange {
|
|
6
|
+
field: string;
|
|
7
|
+
oldValue: unknown;
|
|
8
|
+
newValue: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Represents a change to a node (add/remove/modify)
|
|
12
|
+
*/
|
|
13
|
+
export interface NodeChange {
|
|
14
|
+
type: "add" | "remove" | "modify";
|
|
15
|
+
nodeId: string;
|
|
16
|
+
nodeType: "ASSERTION" | "HTTP_REQUEST" | "WAIT";
|
|
17
|
+
summary: string;
|
|
18
|
+
fieldChanges: FieldChange[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Represents a change to an edge (add/remove)
|
|
22
|
+
*/
|
|
23
|
+
export interface EdgeChange {
|
|
24
|
+
type: "add" | "remove";
|
|
25
|
+
from: string;
|
|
26
|
+
to: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Complete set of changes between local and remote monitors
|
|
30
|
+
*/
|
|
31
|
+
export interface MonitorChanges {
|
|
32
|
+
hasChanges: boolean;
|
|
33
|
+
nodes: NodeChange[];
|
|
34
|
+
edges: EdgeChange[];
|
|
35
|
+
topLevel: FieldChange[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Compare two test monitors and return granular changes.
|
|
39
|
+
* Local monitor should be resolved (variables replaced with actual values).
|
|
40
|
+
*/
|
|
41
|
+
export declare function compareMonitors(local: MonitorV1, remote: MonitorV1): MonitorChanges;
|