@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.
Files changed (49) hide show
  1. package/README.md +122 -367
  2. package/dist/cli.js +48 -29
  3. package/dist/commands/env.d.ts +14 -3
  4. package/dist/commands/env.js +24 -22
  5. package/dist/commands/generate-key.d.ts +5 -6
  6. package/dist/commands/generate-key.js +20 -26
  7. package/dist/commands/hub/apply.d.ts +1 -0
  8. package/dist/commands/hub/apply.js +44 -29
  9. package/dist/commands/hub/connect.d.ts +2 -1
  10. package/dist/commands/hub/connect.js +18 -22
  11. package/dist/commands/hub/destroy.d.ts +2 -1
  12. package/dist/commands/hub/destroy.js +123 -119
  13. package/dist/commands/hub/integrations.d.ts +4 -1
  14. package/dist/commands/hub/integrations.js +160 -141
  15. package/dist/commands/hub/login.d.ts +13 -1
  16. package/dist/commands/hub/login.js +81 -15
  17. package/dist/commands/hub/logout.d.ts +2 -1
  18. package/dist/commands/hub/logout.js +7 -12
  19. package/dist/commands/hub/metrics.js +29 -27
  20. package/dist/commands/hub/monitor.js +16 -16
  21. package/dist/commands/hub/notifications.d.ts +3 -6
  22. package/dist/commands/hub/notifications.js +52 -38
  23. package/dist/commands/hub/run.d.ts +1 -0
  24. package/dist/commands/hub/run.js +114 -87
  25. package/dist/commands/hub/runs.d.ts +2 -0
  26. package/dist/commands/hub/runs.js +47 -45
  27. package/dist/commands/hub/secrets.d.ts +5 -5
  28. package/dist/commands/hub/secrets.js +80 -72
  29. package/dist/commands/hub/status.d.ts +4 -1
  30. package/dist/commands/hub/status.js +15 -9
  31. package/dist/commands/init.d.ts +2 -1
  32. package/dist/commands/init.js +31 -25
  33. package/dist/commands/local/run.d.ts +1 -0
  34. package/dist/commands/local/run.js +34 -26
  35. package/dist/commands/validate.d.ts +4 -1
  36. package/dist/commands/validate.js +23 -14
  37. package/dist/commands/variables.d.ts +17 -3
  38. package/dist/commands/variables.js +29 -28
  39. package/dist/core/credentials.d.ts +15 -0
  40. package/dist/core/credentials.js +37 -0
  41. package/dist/core/variables.js +4 -0
  42. package/dist/monitor-runner.js +0 -12
  43. package/dist/utils/command-wrapper.d.ts +9 -0
  44. package/dist/utils/command-wrapper.js +23 -0
  45. package/dist/utils/output.d.ts +66 -0
  46. package/dist/utils/output.js +202 -0
  47. package/dist/utils/sdk-error.d.ts +6 -1
  48. package/dist/utils/sdk-error.js +107 -77
  49. 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 { withSDKErrorHandling } from "../../utils/sdk-error.js";
5
- import { withCommandErrorHandler, outputJsonOrContinue, } from "../../utils/command-wrapper.js";
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 = withCommandErrorHandler(async (options) => {
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 = terminal.spinner("Fetching metrics...").start();
14
- const response = await withSDKErrorHandling(async () => {
15
- return sdk.getMetricsSummary({
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
- }, "Failed to fetch metrics");
21
+ }
22
+ catch (err) {
23
+ handleSDKErrorWithOutput(err, output, "Failed to fetch metrics");
24
+ }
22
25
  spinner.stop();
23
- const data = response?.data?.data;
24
- if (outputJsonOrContinue(data, options.json))
25
- return;
26
- terminal.info(`Metrics (${options.period})`);
27
- terminal.dim(`${new Date(data.periodStart).toLocaleString()} – ${new Date(data.periodEnd).toLocaleString()}`);
28
- terminal.blank();
29
- terminal.log("Monitors:");
30
- terminal.log(` Total: ${data.monitors.total} Passing: ${terminal.colors.green(String(data.monitors.passing))} Failing: ${data.monitors.failing > 0 ? terminal.colors.red(String(data.monitors.failing)) : "0"} No recent runs: ${data.monitors.noRecentRuns}`);
31
- terminal.blank();
32
- terminal.log("Runs:");
33
- terminal.log(` Total: ${data.runs.total} Success rate: ${data.runs.successRate.toFixed(1)}%`);
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
- terminal.log("Latency (completed runs):");
39
- terminal.log(` p50: ${data.latency.p50DurationMs ?? "-"} ms p95: ${data.latency.p95DurationMs ?? "-"} ms p99: ${data.latency.p99DurationMs ?? "-"} ms`);
40
- terminal.blank();
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
- terminal.log(`Uptime: ${data.uptimePercent.toFixed(1)}%`);
43
- terminal.blank();
44
+ output.log(`Uptime: ${data.uptimePercent.toFixed(1)}%`);
45
+ output.blank();
44
46
  if (data.failingMonitors.length > 0) {
45
- terminal.warn("Failing monitors:");
47
+ output.warn("Failing monitors:");
46
48
  for (const m of data.failingMonitors) {
47
- terminal.log(` ${terminal.colors.red("✗")} ${m.monitorName} (${m.monitorId}) – ${m.consecutiveFailures} consecutive failure(s), last at ${new Date(m.lastFailureAt).toLocaleString()}`);
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 { terminal } from "../../utils/terminal.js";
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 = withCommandErrorHandler(async (options) => {
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
- terminal.blank();
25
- // Output
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
- terminal.log(formatDiffJson(diff));
28
+ output.log(formatDiffJson(diff));
28
29
  }
29
30
  else {
30
- terminal.log(formatDiff(diff));
31
+ output.log(formatDiff(diff));
31
32
  }
32
- // Exit with error code if there are changes
33
- if (diff.summary.creates + diff.summary.updates + diff.summary.deletes >
34
- 0) {
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. Without options, runs a wizard (select integration, then provider-specific prompts).
21
- * With --integration and routing flags, runs non-interactively.
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 = withCommandErrorHandler(async (options) => {
18
+ export const executeNotificationsList = createCommandHandler("notifications list", async (options, output) => {
18
19
  const { sdk } = await createSdkAndState();
19
- const response = await withSDKErrorHandling(() => sdk.getNotificationsRules({
20
- query: {
21
- monitorId: options.monitor,
22
- enabled: options.enabled,
23
- },
24
- }), "Failed to fetch notification rules");
25
- const rules = response?.data?.data || [];
26
- if (outputJsonOrContinue(rules, options.json))
27
- return;
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
- terminal.info("No notification rules found.");
30
- terminal.dim("Define notifications in your monitor DSL and run griffin apply.");
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
- terminal.info("Notification Rules (synced from monitor DSL)");
34
- terminal.blank();
35
- const table = terminal.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
- terminal.log(table.toString());
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. Without options, runs a wizard (select integration, then provider-specific prompts).
143
- * With --integration and routing flags, runs non-interactively.
142
+ * Test a notification integration.
143
+ * In JSON mode --integration (and routing flags) are required to avoid wizard.
144
144
  */
145
- export const executeNotificationsTest = withCommandErrorHandler(async (options) => {
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 = terminal.spinner("Sending test notification...").start();
160
- const response = await withSDKErrorHandling(() => sdk.postNotificationsTest({
161
- body: { integration, routing },
162
- }), "Failed to send test notification");
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?.data?.data;
173
+ const result = response.data?.data;
165
174
  if (result?.success) {
166
- terminal.success(result.message || "Test notification delivered successfully");
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
- terminal.error("Test notification failed");
170
- terminal.exit(1);
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":
@@ -3,6 +3,7 @@ export interface RunOptions {
3
3
  env: string;
4
4
  wait?: boolean;
5
5
  force?: boolean;
6
+ json?: boolean;
6
7
  }
7
8
  /**
8
9
  * Trigger a monitor run on the hub
@@ -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 { terminal } from "../../utils/terminal.js";
5
- import { withSDKErrorHandling } from "../../utils/sdk-error.js";
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 = withCommandErrorHandler(async (options) => {
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
- terminal.error(`Monitor "${options.monitor}" not found locally`);
23
- terminal.blank();
24
- terminal.info("Available monitors:");
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
- terminal.dim(` - ${p.monitor.name}`);
36
+ output.dim(` - ${p.monitor.name}`);
27
37
  }
28
- terminal.exit(1);
38
+ output.exit(1);
29
39
  }
30
- // Fetch remote monitors for this project + environment
31
- const fetchSpinner = terminal.spinner("Checking hub...").start();
32
- const response = await withSDKErrorHandling(() => sdk.getMonitor({
33
- query: {
34
- projectId: state.projectId,
35
- environment: envName,
36
- },
37
- }), "Failed to fetch monitors from hub");
38
- const remoteMonitors = response?.data?.data;
39
- // Find remote monitor by name
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
- terminal.dim("Run 'griffin apply' to sync your monitors first");
44
- terminal.exit(1);
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
- // Compute diff to check if local monitor differs from remote
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
- terminal.error(`Local monitor "${options.monitor}" differs from hub`);
58
- terminal.blank();
59
- terminal.warn("The monitor on the hub is different from your local version.");
60
- terminal.dim("Run 'griffin apply' to sync, or use --force to run anyway.");
61
- terminal.exit(1);
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
- // Trigger the run
64
- terminal.blank();
65
- terminal.info(`Triggering run for monitor: ${terminal.colors.cyan(options.monitor)}`);
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
- terminal.warn("Running with --force (local changes not applied)");
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
- const triggerSpinner = terminal.spinner("Triggering run...").start();
71
- const runResponse = await withSDKErrorHandling(() => sdk.postRunsTriggerByMonitorId({
72
- path: {
73
- monitorId: remoteMonitor.id,
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
- terminal.blank();
82
- terminal.log(`Run ID: ${terminal.colors.dim(run.id)}`);
83
- terminal.log(`Status: ${terminal.colors.cyan(run.status)}`);
84
- terminal.log(`Started: ${terminal.colors.dim(new Date(run.startedAt).toLocaleString())}`);
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
- terminal.blank();
88
- const waitSpinner = terminal
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
- let completed = false;
93
- while (!completed) {
94
- await new Promise((resolve) => setTimeout(resolve, 2000)); // Poll every 2 seconds
95
- const runStatusResponse = await withSDKErrorHandling(() => sdk.getRunsById({
96
- path: {
97
- id: runId,
98
- },
99
- }), "Failed to fetch run status");
100
- const run = runStatusResponse?.data?.data;
101
- if (run.status === "completed" || run.status === "failed") {
102
- completed = true;
103
- if (run.success) {
104
- waitSpinner.succeed(`Run ${run.status}`);
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 ${run.status}`);
127
+ waitSpinner.fail(`Run ${currentRun.status}`);
108
128
  }
109
- terminal.blank();
110
- if (run.duration_ms) {
111
- terminal.log(`Duration: ${terminal.colors.dim((run.duration_ms / 1000).toFixed(2) + "s")}`);
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 (run.success !== undefined) {
114
- const successText = run.success
115
- ? terminal.colors.green("Yes")
116
- : terminal.colors.red("No");
117
- terminal.log(`Success: ${successText}`);
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 (run.errors && run.errors.length > 0) {
120
- terminal.blank();
121
- terminal.error("Errors:");
122
- for (const error of run.errors) {
123
- terminal.dim(` - ${error}`);
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
- if (!run.success) {
127
- terminal.exit(1);
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
- terminal.blank();
134
- terminal.dim("Run started. Use 'griffin runs' to check progress.");
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
  });
@@ -1,6 +1,8 @@
1
1
  export interface RunsOptions {
2
2
  monitor?: string;
3
3
  limit?: number;
4
+ env?: string;
5
+ json?: boolean;
4
6
  }
5
7
  /**
6
8
  * Show recent runs from the hub