@griffin-app/griffin-cli 1.0.26 → 1.0.28
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/README.md +122 -367
- package/dist/cli.js +48 -29
- package/dist/commands/env.d.ts +14 -3
- package/dist/commands/env.js +24 -22
- package/dist/commands/generate-key.d.ts +5 -6
- package/dist/commands/generate-key.js +20 -26
- package/dist/commands/hub/apply.d.ts +1 -0
- package/dist/commands/hub/apply.js +44 -29
- package/dist/commands/hub/connect.d.ts +2 -1
- package/dist/commands/hub/connect.js +18 -22
- package/dist/commands/hub/destroy.d.ts +2 -1
- package/dist/commands/hub/destroy.js +123 -119
- package/dist/commands/hub/integrations.d.ts +4 -1
- package/dist/commands/hub/integrations.js +160 -141
- package/dist/commands/hub/login.d.ts +13 -1
- package/dist/commands/hub/login.js +81 -15
- package/dist/commands/hub/logout.d.ts +2 -1
- package/dist/commands/hub/logout.js +7 -12
- package/dist/commands/hub/metrics.js +29 -27
- package/dist/commands/hub/monitor.js +16 -16
- package/dist/commands/hub/notifications.d.ts +3 -6
- package/dist/commands/hub/notifications.js +52 -38
- package/dist/commands/hub/run.d.ts +1 -0
- package/dist/commands/hub/run.js +114 -87
- package/dist/commands/hub/runs.d.ts +2 -0
- package/dist/commands/hub/runs.js +47 -45
- package/dist/commands/hub/secrets.d.ts +5 -5
- package/dist/commands/hub/secrets.js +80 -72
- package/dist/commands/hub/status.d.ts +4 -1
- package/dist/commands/hub/status.js +15 -9
- package/dist/commands/init.d.ts +2 -1
- package/dist/commands/init.js +31 -25
- package/dist/commands/local/run.d.ts +1 -0
- package/dist/commands/local/run.js +34 -26
- package/dist/commands/validate.d.ts +4 -1
- package/dist/commands/validate.js +23 -14
- package/dist/commands/variables.d.ts +17 -3
- package/dist/commands/variables.js +29 -28
- package/dist/core/credentials.d.ts +15 -0
- package/dist/core/credentials.js +37 -0
- package/dist/core/variables.js +4 -0
- package/dist/monitor-runner.js +0 -12
- package/dist/utils/command-wrapper.d.ts +9 -0
- package/dist/utils/command-wrapper.js +23 -0
- package/dist/utils/output.d.ts +66 -0
- package/dist/utils/output.js +202 -0
- package/dist/utils/sdk-error.d.ts +6 -1
- package/dist/utils/sdk-error.js +107 -77
- package/package.json +2 -2
|
@@ -1,50 +1,52 @@
|
|
|
1
1
|
import { resolveEnvironment } from "../../core/state.js";
|
|
2
|
-
import { terminal } from "../../utils/terminal.js";
|
|
3
2
|
import { createSdkFromState } from "../../core/sdk.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { handleSDKErrorWithOutput } from "../../utils/sdk-error.js";
|
|
4
|
+
import { createCommandHandler, } from "../../utils/command-wrapper.js";
|
|
6
5
|
const METRICS_PERIODS = ["1h", "6h", "24h", "7d", "30d"];
|
|
7
6
|
/**
|
|
8
7
|
* Show metrics summary from the hub
|
|
9
8
|
*/
|
|
10
|
-
export const executeMetrics =
|
|
9
|
+
export const executeMetrics = createCommandHandler("metrics", async (options, output) => {
|
|
11
10
|
const sdk = await createSdkFromState();
|
|
12
11
|
const env = await resolveEnvironment(options.environment);
|
|
13
|
-
const spinner =
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const spinner = output.spinner("Fetching metrics...").start();
|
|
13
|
+
let response;
|
|
14
|
+
try {
|
|
15
|
+
response = await sdk.getMetricsSummary({
|
|
16
16
|
query: {
|
|
17
17
|
environment: env,
|
|
18
18
|
period: options.period,
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
|
-
}
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
handleSDKErrorWithOutput(err, output, "Failed to fetch metrics");
|
|
24
|
+
}
|
|
22
25
|
spinner.stop();
|
|
23
|
-
const data = response
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
terminal.blank();
|
|
26
|
+
const data = response.data?.data;
|
|
27
|
+
output.setData(data);
|
|
28
|
+
output.info(`Metrics (${options.period})`);
|
|
29
|
+
output.dim(`${new Date(data.periodStart).toLocaleString()} – ${new Date(data.periodEnd).toLocaleString()}`);
|
|
30
|
+
output.blank();
|
|
31
|
+
output.log("Monitors:");
|
|
32
|
+
output.log(` Total: ${data.monitors.total} Passing: ${output.colors.green(String(data.monitors.passing))} Failing: ${data.monitors.failing > 0 ? output.colors.red(String(data.monitors.failing)) : "0"} No recent runs: ${data.monitors.noRecentRuns}`);
|
|
33
|
+
output.blank();
|
|
34
|
+
output.log("Runs:");
|
|
35
|
+
output.log(` Total: ${data.runs.total} Success rate: ${data.runs.successRate.toFixed(1)}%`);
|
|
36
|
+
output.blank();
|
|
35
37
|
if (data.latency.p50DurationMs != null ||
|
|
36
38
|
data.latency.p95DurationMs != null ||
|
|
37
39
|
data.latency.p99DurationMs != null) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
output.log("Latency (completed runs):");
|
|
41
|
+
output.log(` p50: ${data.latency.p50DurationMs ?? "-"} ms p95: ${data.latency.p95DurationMs ?? "-"} ms p99: ${data.latency.p99DurationMs ?? "-"} ms`);
|
|
42
|
+
output.blank();
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
output.log(`Uptime: ${data.uptimePercent.toFixed(1)}%`);
|
|
45
|
+
output.blank();
|
|
44
46
|
if (data.failingMonitors.length > 0) {
|
|
45
|
-
|
|
47
|
+
output.warn("Failing monitors:");
|
|
46
48
|
for (const m of data.failingMonitors) {
|
|
47
|
-
|
|
49
|
+
output.log(` ${output.colors.red("✗")} ${m.monitorName} (${m.monitorId}) – ${m.consecutiveFailures} consecutive failure(s), last at ${new Date(m.lastFailureAt).toLocaleString()}`);
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
});
|
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
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
|
-
import {
|
|
5
|
-
import { withCommandErrorHandler } from "../../utils/command-wrapper.js";
|
|
4
|
+
import { createCommandHandler } from "../../utils/command-wrapper.js";
|
|
6
5
|
import { discoverLocalMonitors, fetchRemoteMonitors, resolveLocalMonitors, } from "../../core/monitor-helpers.js";
|
|
7
6
|
/**
|
|
8
7
|
* Show what changes would be applied
|
|
9
8
|
*/
|
|
10
|
-
export const executePlan =
|
|
9
|
+
export const executePlan = createCommandHandler("plan", async (options, output) => {
|
|
11
10
|
const { sdk, state } = await createSdkAndState();
|
|
12
|
-
// Resolve environment
|
|
13
11
|
const envName = await resolveEnvironment(options.env);
|
|
14
|
-
// Discover local monitors
|
|
15
12
|
const { monitors } = await discoverLocalMonitors(state);
|
|
16
|
-
// Fetch remote monitors for this project + environment
|
|
17
13
|
const remoteMonitors = await fetchRemoteMonitors(sdk, state.projectId, envName);
|
|
18
|
-
// Load variables and resolve local monitors before computing diff
|
|
19
14
|
const resolvedMonitors = await resolveLocalMonitors(monitors, state.projectId, envName);
|
|
20
|
-
// Compute diff (no deletions shown by default)
|
|
21
15
|
const diff = computeDiff(resolvedMonitors, remoteMonitors, {
|
|
22
16
|
includeDeletions: false,
|
|
23
17
|
});
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
output.setData({
|
|
19
|
+
summary: diff.summary,
|
|
20
|
+
actions: diff.actions.map((a) => ({
|
|
21
|
+
type: a.type,
|
|
22
|
+
monitorName: a.monitor?.name ?? a.remoteMonitor?.name,
|
|
23
|
+
reason: a.reason,
|
|
24
|
+
})),
|
|
25
|
+
});
|
|
26
|
+
output.blank();
|
|
26
27
|
if (options.json) {
|
|
27
|
-
|
|
28
|
+
output.log(formatDiffJson(diff));
|
|
28
29
|
}
|
|
29
30
|
else {
|
|
30
|
-
|
|
31
|
+
output.log(formatDiff(diff));
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
terminal.exit(2); // Exit code 2 indicates changes pending
|
|
33
|
+
const changeCount = diff.summary.creates + diff.summary.updates + diff.summary.deletes;
|
|
34
|
+
if (changeCount > 0) {
|
|
35
|
+
output.exit(2);
|
|
36
36
|
}
|
|
37
37
|
});
|
|
@@ -4,20 +4,17 @@ export interface NotificationsListOptions {
|
|
|
4
4
|
json?: boolean;
|
|
5
5
|
}
|
|
6
6
|
export interface NotificationsTestOptions {
|
|
7
|
-
/** Integration id or name. If omitted, wizard runs (select integration + provider options). */
|
|
8
7
|
integration?: string;
|
|
9
|
-
/** Slack channel (e.g. #alerts). Use with Slack integration. */
|
|
10
8
|
channel?: string;
|
|
11
|
-
/** Email recipients, comma-separated. Use with email integration. */
|
|
12
9
|
toAddresses?: string;
|
|
10
|
+
json?: boolean;
|
|
13
11
|
}
|
|
14
12
|
/**
|
|
15
13
|
* List notification rules (read-only).
|
|
16
|
-
* Rules are defined in monitor DSL and synced via `griffin apply`.
|
|
17
14
|
*/
|
|
18
15
|
export declare const executeNotificationsList: (options: NotificationsListOptions) => Promise<void>;
|
|
19
16
|
/**
|
|
20
|
-
* Test a notification integration.
|
|
21
|
-
*
|
|
17
|
+
* Test a notification integration.
|
|
18
|
+
* In JSON mode --integration (and routing flags) are required to avoid wizard.
|
|
22
19
|
*/
|
|
23
20
|
export declare const executeNotificationsTest: (options: NotificationsTestOptions) => Promise<void>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createSdkAndState } from "../../core/sdk.js";
|
|
2
2
|
import { terminal } from "../../utils/terminal.js";
|
|
3
|
+
import { handleSDKErrorWithOutput } from "../../utils/sdk-error.js";
|
|
4
|
+
import { createCommandHandler } from "../../utils/command-wrapper.js";
|
|
5
|
+
import { OutputError } from "../../utils/output.js";
|
|
3
6
|
import { withSDKErrorHandling } from "../../utils/sdk-error.js";
|
|
4
|
-
import { withCommandErrorHandler, outputJsonOrContinue, } from "../../utils/command-wrapper.js";
|
|
5
7
|
function orExit(value, errorMessage) {
|
|
6
8
|
if (value === undefined) {
|
|
7
9
|
terminal.error(errorMessage);
|
|
@@ -12,27 +14,31 @@ function orExit(value, errorMessage) {
|
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* List notification rules (read-only).
|
|
15
|
-
* Rules are defined in monitor DSL and synced via `griffin apply`.
|
|
16
17
|
*/
|
|
17
|
-
export const executeNotificationsList =
|
|
18
|
+
export const executeNotificationsList = createCommandHandler("notifications list", async (options, output) => {
|
|
18
19
|
const { sdk } = await createSdkAndState();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
let response;
|
|
21
|
+
try {
|
|
22
|
+
response = await sdk.getNotificationsRules({
|
|
23
|
+
query: {
|
|
24
|
+
monitorId: options.monitor,
|
|
25
|
+
enabled: options.enabled,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
handleSDKErrorWithOutput(err, output, "Failed to fetch notification rules");
|
|
31
|
+
}
|
|
32
|
+
const rules = response.data?.data || [];
|
|
33
|
+
output.setData({ rules });
|
|
28
34
|
if (rules.length === 0) {
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
output.info("No notification rules found.");
|
|
36
|
+
output.dim("Define notifications in your monitor DSL and run griffin apply.");
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const table =
|
|
39
|
+
output.info("Notification Rules (synced from monitor DSL)");
|
|
40
|
+
output.blank();
|
|
41
|
+
const table = output.table({
|
|
36
42
|
head: ["ID", "Monitor", "Integration", "Trigger", "Enabled"],
|
|
37
43
|
});
|
|
38
44
|
for (const rule of rules) {
|
|
@@ -46,7 +52,7 @@ export const executeNotificationsList = withCommandErrorHandler(async (options)
|
|
|
46
52
|
enabled,
|
|
47
53
|
]);
|
|
48
54
|
}
|
|
49
|
-
|
|
55
|
+
output.log(table.toString());
|
|
50
56
|
});
|
|
51
57
|
async function fetchNotificationIntegrations(sdk) {
|
|
52
58
|
const result = await withSDKErrorHandling(() => sdk.getIntegrations({
|
|
@@ -85,9 +91,6 @@ async function promptRoutingForProvider(provider) {
|
|
|
85
91
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
86
92
|
}
|
|
87
93
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Build routing object from CLI options. Exactly one of channel, toAddresses, or webhook (none) must be used.
|
|
90
|
-
*/
|
|
91
94
|
function buildRoutingFromOptions(options) {
|
|
92
95
|
if (options.channel !== undefined && options.channel !== "") {
|
|
93
96
|
return { channelType: "slack", channel: options.channel };
|
|
@@ -103,9 +106,6 @@ function buildRoutingFromOptions(options) {
|
|
|
103
106
|
}
|
|
104
107
|
return { channelType: "webhook" };
|
|
105
108
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Run interactive wizard: select integration, then prompt for provider-specific options (channel, to-addresses, or none).
|
|
108
|
-
*/
|
|
109
109
|
async function executeNotificationsTestWizard(sdk, options) {
|
|
110
110
|
const integrations = await fetchNotificationIntegrations(sdk);
|
|
111
111
|
if (integrations.length === 0) {
|
|
@@ -139,12 +139,15 @@ function canBuildRoutingFromOptions(options) {
|
|
|
139
139
|
return false;
|
|
140
140
|
}
|
|
141
141
|
/**
|
|
142
|
-
* Test a notification integration.
|
|
143
|
-
*
|
|
142
|
+
* Test a notification integration.
|
|
143
|
+
* In JSON mode --integration (and routing flags) are required to avoid wizard.
|
|
144
144
|
*/
|
|
145
|
-
export const executeNotificationsTest =
|
|
145
|
+
export const executeNotificationsTest = createCommandHandler("notifications test", async (options, output) => {
|
|
146
146
|
const { sdk } = await createSdkAndState();
|
|
147
147
|
const useWizard = !options.integration || !canBuildRoutingFromOptions(options);
|
|
148
|
+
if (options.json && useWizard) {
|
|
149
|
+
throw new OutputError("INTERACTIVE_REQUIRED", "In JSON mode provide --integration and routing (e.g. --channel or --to-addresses) to run non-interactively.", undefined, "Select integration and provide channel/recipients");
|
|
150
|
+
}
|
|
148
151
|
let integration;
|
|
149
152
|
let routing;
|
|
150
153
|
if (useWizard) {
|
|
@@ -156,23 +159,34 @@ export const executeNotificationsTest = withCommandErrorHandler(async (options)
|
|
|
156
159
|
integration = options.integration;
|
|
157
160
|
routing = buildRoutingFromOptions(options);
|
|
158
161
|
}
|
|
159
|
-
const spinner =
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
const spinner = output.spinner("Sending test notification...").start();
|
|
163
|
+
let response;
|
|
164
|
+
try {
|
|
165
|
+
response = await sdk.postNotificationsTest({
|
|
166
|
+
body: { integration, routing },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
handleSDKErrorWithOutput(err, output, "Failed to send test notification");
|
|
171
|
+
}
|
|
163
172
|
spinner.stop();
|
|
164
|
-
const result = response
|
|
173
|
+
const result = response.data?.data;
|
|
165
174
|
if (result?.success) {
|
|
166
|
-
|
|
175
|
+
output.setData({
|
|
176
|
+
success: true,
|
|
177
|
+
message: result.message ?? "Test notification delivered successfully",
|
|
178
|
+
});
|
|
179
|
+
output.success(result.message || "Test notification delivered successfully");
|
|
167
180
|
}
|
|
168
181
|
else {
|
|
169
|
-
|
|
170
|
-
|
|
182
|
+
output.setData({
|
|
183
|
+
success: false,
|
|
184
|
+
message: "Test notification failed",
|
|
185
|
+
});
|
|
186
|
+
output.error("Test notification failed");
|
|
187
|
+
output.exit(1);
|
|
171
188
|
}
|
|
172
189
|
});
|
|
173
|
-
/**
|
|
174
|
-
* Format trigger for display
|
|
175
|
-
*/
|
|
176
190
|
function formatTrigger(trigger) {
|
|
177
191
|
switch (trigger.type) {
|
|
178
192
|
case "run_failed":
|
package/dist/commands/hub/run.js
CHANGED
|
@@ -1,136 +1,163 @@
|
|
|
1
1
|
import { resolveEnvironment } from "../../core/state.js";
|
|
2
2
|
import { createSdkAndState } from "../../core/sdk.js";
|
|
3
3
|
import { computeDiff } from "../../core/diff.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { withCommandErrorHandler } from "../../utils/command-wrapper.js";
|
|
4
|
+
import { handleSDKErrorWithOutput } from "../../utils/sdk-error.js";
|
|
5
|
+
import { createCommandHandler } from "../../utils/command-wrapper.js";
|
|
7
6
|
import { discoverLocalMonitors, } from "../../core/monitor-helpers.js";
|
|
8
7
|
import { loadVariables } from "../../core/variables.js";
|
|
9
8
|
import { resolveMonitor } from "../../resolve.js";
|
|
9
|
+
function runToData(run) {
|
|
10
|
+
return {
|
|
11
|
+
id: run.id,
|
|
12
|
+
status: run.status,
|
|
13
|
+
success: run.success,
|
|
14
|
+
startedAt: run.startedAt,
|
|
15
|
+
durationMs: run.duration_ms ?? null,
|
|
16
|
+
errors: run.errors ?? [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
10
19
|
/**
|
|
11
20
|
* Trigger a monitor run on the hub
|
|
12
21
|
*/
|
|
13
|
-
export const executeRun =
|
|
22
|
+
export const executeRun = createCommandHandler("run", async (options, output) => {
|
|
14
23
|
const { sdk, state } = await createSdkAndState();
|
|
15
|
-
// Resolve environment
|
|
16
24
|
const envName = await resolveEnvironment(options.env);
|
|
17
|
-
// Discover local monitors
|
|
18
25
|
const { monitors: discoveredMonitors } = await discoverLocalMonitors(state);
|
|
19
|
-
// Find local monitor by name
|
|
20
26
|
const localMonitor = discoveredMonitors.find((p) => p.monitor.name === options.monitor);
|
|
21
27
|
if (!localMonitor) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
output.setData({
|
|
29
|
+
error: "NOT_FOUND",
|
|
30
|
+
message: `Monitor "${options.monitor}" not found locally`,
|
|
31
|
+
});
|
|
32
|
+
output.error(`Monitor "${options.monitor}" not found locally`);
|
|
33
|
+
output.blank();
|
|
34
|
+
output.info("Available monitors:");
|
|
25
35
|
for (const p of discoveredMonitors) {
|
|
26
|
-
|
|
36
|
+
output.dim(` - ${p.monitor.name}`);
|
|
27
37
|
}
|
|
28
|
-
|
|
38
|
+
output.exit(1);
|
|
29
39
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
const fetchSpinner = output.spinner("Checking hub...").start();
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = await sdk.getMonitor({
|
|
44
|
+
query: {
|
|
45
|
+
projectId: state.projectId,
|
|
46
|
+
environment: envName,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
handleSDKErrorWithOutput(err, output, "Failed to fetch monitors from hub");
|
|
52
|
+
}
|
|
53
|
+
const remoteMonitors = response.data?.data;
|
|
40
54
|
const remoteMonitor = remoteMonitors.find((p) => p.name === options.monitor);
|
|
41
55
|
if (!remoteMonitor) {
|
|
42
56
|
fetchSpinner.fail(`Monitor "${options.monitor}" not found on hub`);
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
output.setData({
|
|
58
|
+
error: "NOT_FOUND",
|
|
59
|
+
message: `Monitor "${options.monitor}" not found on hub`,
|
|
60
|
+
});
|
|
61
|
+
output.dim("Run 'griffin apply' to sync your monitors first");
|
|
62
|
+
output.exit(1);
|
|
45
63
|
}
|
|
46
64
|
fetchSpinner.succeed("Monitor found on hub");
|
|
47
|
-
// Load variables and resolve local monitor before computing diff
|
|
48
65
|
const variables = await loadVariables(envName);
|
|
49
66
|
const resolvedLocalMonitor = resolveMonitor(localMonitor.monitor, state.projectId, envName, variables);
|
|
50
|
-
|
|
51
|
-
const diff = computeDiff([resolvedLocalMonitor], [remoteMonitor], {
|
|
52
|
-
includeDeletions: false,
|
|
53
|
-
});
|
|
67
|
+
const diff = computeDiff([resolvedLocalMonitor], [remoteMonitor], { includeDeletions: false });
|
|
54
68
|
const hasDiff = diff.actions.length > 0 &&
|
|
55
69
|
diff.actions.some((a) => a.type === "update" || a.type === "create");
|
|
56
70
|
if (hasDiff && !options.force) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
output.setData({
|
|
72
|
+
error: "LOCAL_DIFFERS",
|
|
73
|
+
message: `Local monitor "${options.monitor}" differs from hub`,
|
|
74
|
+
});
|
|
75
|
+
output.error(`Local monitor "${options.monitor}" differs from hub`);
|
|
76
|
+
output.blank();
|
|
77
|
+
output.warn("The monitor on the hub is different from your local version.");
|
|
78
|
+
output.dim("Run 'griffin apply' to sync, or use --force to run anyway.");
|
|
79
|
+
output.exit(1);
|
|
62
80
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
terminal.log(`Target environment: ${terminal.colors.cyan(envName)}`);
|
|
81
|
+
output.blank();
|
|
82
|
+
output.info(`Triggering run for monitor: ${output.colors.cyan(options.monitor)}`);
|
|
83
|
+
output.log(`Target environment: ${output.colors.cyan(envName)}`);
|
|
67
84
|
if (hasDiff && options.force) {
|
|
68
|
-
|
|
85
|
+
output.warn("Running with --force (local changes not applied)");
|
|
86
|
+
}
|
|
87
|
+
const triggerSpinner = output.spinner("Triggering run...").start();
|
|
88
|
+
let runResponse;
|
|
89
|
+
try {
|
|
90
|
+
runResponse = await sdk.postRunsTriggerByMonitorId({
|
|
91
|
+
path: { monitorId: remoteMonitor.id },
|
|
92
|
+
body: { environment: envName },
|
|
93
|
+
});
|
|
69
94
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
body: {
|
|
76
|
-
environment: envName,
|
|
77
|
-
},
|
|
78
|
-
}), "Failed to trigger run");
|
|
79
|
-
const run = runResponse?.data?.data;
|
|
95
|
+
catch (err) {
|
|
96
|
+
handleSDKErrorWithOutput(err, output, "Failed to trigger run");
|
|
97
|
+
}
|
|
98
|
+
const run = runResponse.data?.data;
|
|
80
99
|
triggerSpinner.succeed("Run triggered");
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Wait for completion if requested
|
|
100
|
+
output.blank();
|
|
101
|
+
output.log(`Run ID: ${output.colors.dim(run.id)}`);
|
|
102
|
+
output.log(`Status: ${output.colors.cyan(run.status)}`);
|
|
103
|
+
output.log(`Started: ${output.colors.dim(new Date(run.startedAt).toLocaleString())}`);
|
|
86
104
|
if (options.wait) {
|
|
87
|
-
|
|
88
|
-
const waitSpinner =
|
|
105
|
+
output.blank();
|
|
106
|
+
const waitSpinner = output
|
|
89
107
|
.spinner("Waiting for run to complete...")
|
|
90
108
|
.start();
|
|
91
109
|
const runId = run.id;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
id: runId,
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
while (true) {
|
|
111
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
112
|
+
let runStatusResponse;
|
|
113
|
+
try {
|
|
114
|
+
runStatusResponse = await sdk.getRunsById({
|
|
115
|
+
path: { id: runId },
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
handleSDKErrorWithOutput(err, output, "Failed to fetch run status");
|
|
120
|
+
}
|
|
121
|
+
const currentRun = runStatusResponse.data?.data;
|
|
122
|
+
if (currentRun.status === "completed" || currentRun.status === "failed") {
|
|
123
|
+
if (currentRun.success) {
|
|
124
|
+
waitSpinner.succeed(`Run ${currentRun.status}`);
|
|
105
125
|
}
|
|
106
126
|
else {
|
|
107
|
-
waitSpinner.fail(`Run ${
|
|
127
|
+
waitSpinner.fail(`Run ${currentRun.status}`);
|
|
108
128
|
}
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
129
|
+
output.blank();
|
|
130
|
+
if (currentRun.duration_ms) {
|
|
131
|
+
output.log(`Duration: ${output.colors.dim((currentRun.duration_ms / 1000).toFixed(2) + "s")}`);
|
|
112
132
|
}
|
|
113
|
-
if (
|
|
114
|
-
const successText =
|
|
115
|
-
?
|
|
116
|
-
:
|
|
117
|
-
|
|
133
|
+
if (currentRun.success !== undefined) {
|
|
134
|
+
const successText = currentRun.success
|
|
135
|
+
? output.colors.green("Yes")
|
|
136
|
+
: output.colors.red("No");
|
|
137
|
+
output.log(`Success: ${successText}`);
|
|
118
138
|
}
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
for (const error of
|
|
123
|
-
|
|
139
|
+
if (currentRun.errors && currentRun.errors.length > 0) {
|
|
140
|
+
output.blank();
|
|
141
|
+
output.error("Errors:");
|
|
142
|
+
for (const error of currentRun.errors) {
|
|
143
|
+
output.dim(` - ${error}`);
|
|
124
144
|
}
|
|
125
145
|
}
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
output.setData({
|
|
147
|
+
run: runToData(currentRun),
|
|
148
|
+
});
|
|
149
|
+
if (!currentRun.success) {
|
|
150
|
+
output.exit(1);
|
|
128
151
|
}
|
|
152
|
+
break;
|
|
129
153
|
}
|
|
130
154
|
}
|
|
131
155
|
}
|
|
132
156
|
else {
|
|
133
|
-
|
|
134
|
-
|
|
157
|
+
output.setData({
|
|
158
|
+
run: runToData(run),
|
|
159
|
+
});
|
|
160
|
+
output.blank();
|
|
161
|
+
output.dim("Run started. Use 'griffin runs' to check progress.");
|
|
135
162
|
}
|
|
136
163
|
});
|