@griffin-app/griffin-cli 1.0.6 → 1.0.8
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 +38 -38
- package/dist/cli.js +79 -11
- package/dist/commands/apply.js +7 -7
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/hub/apply.d.ts +1 -1
- package/dist/commands/hub/apply.js +17 -17
- package/dist/commands/hub/login.js +2 -6
- package/dist/commands/hub/metrics.d.ts +12 -0
- package/dist/commands/hub/metrics.js +78 -0
- package/dist/commands/hub/monitor.d.ts +8 -0
- package/dist/commands/hub/monitor.js +75 -0
- package/dist/commands/hub/notifications.d.ts +36 -0
- package/dist/commands/hub/notifications.js +225 -0
- package/dist/commands/hub/plan.d.ts +2 -2
- package/dist/commands/hub/plan.js +16 -16
- package/dist/commands/hub/run.d.ts +2 -2
- package/dist/commands/hub/run.js +33 -33
- package/dist/commands/hub/runs.d.ts +1 -1
- package/dist/commands/hub/runs.js +4 -4
- package/dist/commands/init.js +28 -12
- package/dist/commands/local/run.d.ts +2 -2
- package/dist/commands/local/run.js +1 -1
- package/dist/commands/plan.d.ts +2 -2
- package/dist/commands/plan.js +7 -7
- package/dist/commands/run-remote.d.ts +1 -1
- package/dist/commands/run-remote.js +10 -10
- package/dist/commands/validate.d.ts +1 -1
- package/dist/commands/validate.js +12 -12
- package/dist/core/apply.d.ts +2 -2
- package/dist/core/apply.js +25 -25
- package/dist/core/apply.test.js +54 -54
- package/dist/core/diff.d.ts +14 -14
- package/dist/core/diff.js +42 -42
- package/dist/core/diff.test.js +34 -34
- package/dist/core/discovery.d.ts +6 -6
- package/dist/core/discovery.js +20 -20
- package/dist/core/monitor-diff.d.ts +41 -0
- package/dist/core/monitor-diff.js +257 -0
- package/dist/core/plan-diff.d.ts +6 -6
- package/dist/core/plan-diff.js +5 -5
- package/dist/core/state.js +2 -2
- package/dist/core/variables.d.ts +5 -5
- package/dist/core/variables.js +7 -7
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- 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/resolve.d.ts +3 -3
- package/dist/resolve.js +9 -9
- package/dist/schemas/payload.d.ts +3 -3
- package/dist/schemas/payload.js +3 -3
- package/dist/schemas/state.d.ts +1 -1
- package/dist/schemas/state.js +2 -2
- package/dist/test-runner.d.ts +1 -1
- package/dist/test-runner.js +13 -13
- package/dist/utils/sdk-error.js +2 -0
- package/package.json +4 -4
|
@@ -3,7 +3,7 @@ import { runTestFile } from "../../test-runner.js";
|
|
|
3
3
|
import { resolveEnvironment } from "../../core/state.js";
|
|
4
4
|
import { terminal } from "../../utils/terminal.js";
|
|
5
5
|
import { basename } from "path";
|
|
6
|
-
export async function executeRunLocal(options
|
|
6
|
+
export async function executeRunLocal(options) {
|
|
7
7
|
try {
|
|
8
8
|
// Resolve environment
|
|
9
9
|
const envName = await resolveEnvironment(options.env);
|
package/dist/commands/plan.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface MonitorOptions {
|
|
2
2
|
json?: boolean;
|
|
3
3
|
env?: string;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
6
|
* Show what changes would be applied
|
|
7
7
|
*/
|
|
8
|
-
export declare function
|
|
8
|
+
export declare function executeMonitor(options: MonitorOptions): Promise<void>;
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { loadState, resolveEnvironment } from "../core/state.js";
|
|
2
|
-
import {
|
|
2
|
+
import { discoverMonitors, formatDiscoveryErrors } from "../core/discovery.js";
|
|
3
3
|
import { createSdkClients } from "../core/sdk.js";
|
|
4
4
|
import { computeDiff, formatDiff, formatDiffJson } from "../core/diff.js";
|
|
5
5
|
/**
|
|
6
6
|
* Show what changes would be applied
|
|
7
7
|
*/
|
|
8
|
-
export async function
|
|
8
|
+
export async function executeMonitor(options) {
|
|
9
9
|
try {
|
|
10
10
|
// Load state
|
|
11
11
|
const state = await loadState();
|
|
@@ -17,13 +17,13 @@ export async function executePlan(options) {
|
|
|
17
17
|
console.log(" griffin runner set --base-url <url> --api-token <token>");
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
|
-
// Discover local
|
|
20
|
+
// Discover local monitors
|
|
21
21
|
const discoveryPattern = state.discovery?.pattern || "**/__griffin__/*.{ts,js}";
|
|
22
22
|
const discoveryIgnore = state.discovery?.ignore || [
|
|
23
23
|
"node_modules/**",
|
|
24
24
|
"dist/**",
|
|
25
25
|
];
|
|
26
|
-
const {
|
|
26
|
+
const { monitors, errors } = await discoverMonitors(discoveryPattern, discoveryIgnore);
|
|
27
27
|
if (errors.length > 0) {
|
|
28
28
|
console.error(formatDiscoveryErrors(errors));
|
|
29
29
|
process.exit(1);
|
|
@@ -33,11 +33,11 @@ export async function executePlan(options) {
|
|
|
33
33
|
baseUrl: state.runner.baseUrl,
|
|
34
34
|
apiToken: state.runner.apiToken || undefined,
|
|
35
35
|
});
|
|
36
|
-
// Fetch remote
|
|
36
|
+
// Fetch remote monitors for this project
|
|
37
37
|
const response = await planApi.planGet(state.projectId);
|
|
38
|
-
const
|
|
38
|
+
const remoteMonitors = response.data.data.map((p) => p);
|
|
39
39
|
// Compute diff for this environment
|
|
40
|
-
const diff = computeDiff(
|
|
40
|
+
const diff = computeDiff(monitors.map((p) => p.monitor), state, remoteMonitors, envName);
|
|
41
41
|
// Output
|
|
42
42
|
if (options.json) {
|
|
43
43
|
console.log(formatDiffJson(diff));
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { loadState, resolveEnvironment } from "../core/state.js";
|
|
2
2
|
import { createSdkClients } from "../core/sdk.js";
|
|
3
3
|
/**
|
|
4
|
-
* Trigger a
|
|
4
|
+
* Trigger a monitor run on the runner
|
|
5
5
|
*/
|
|
6
6
|
export async function executeRun(options) {
|
|
7
7
|
try {
|
|
8
|
-
// TODO: remove this. Users shoud be able to execute all
|
|
8
|
+
// TODO: remove this. Users shoud be able to execute all monitors if they want.
|
|
9
9
|
if (!options.planId && !options.planName) {
|
|
10
|
-
console.error("Error: Either --
|
|
10
|
+
console.error("Error: Either --monitor-id or --monitor-name must be provided");
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
13
|
if (!options.targetEnv) {
|
|
@@ -27,24 +27,24 @@ export async function executeRun(options) {
|
|
|
27
27
|
baseUrl: state.runner.baseUrl,
|
|
28
28
|
apiToken: state.runner.apiToken || undefined,
|
|
29
29
|
});
|
|
30
|
-
// Resolve
|
|
30
|
+
// Resolve monitor ID from name if needed
|
|
31
31
|
let planId = options.planId;
|
|
32
32
|
if (!planId && options.planName) {
|
|
33
33
|
// Resolve environment to search in
|
|
34
34
|
const envName = await resolveEnvironment(options.env);
|
|
35
|
-
const
|
|
36
|
-
const stateEntry =
|
|
35
|
+
const envMonitors = state.monitors[envName] || [];
|
|
36
|
+
const stateEntry = envMonitors.find((p) => p.planName === options.planName);
|
|
37
37
|
if (!stateEntry) {
|
|
38
|
-
console.error(`Error:
|
|
39
|
-
console.error("Run 'griffin apply' to sync your
|
|
38
|
+
console.error(`Error: Monitor "${options.planName}" not found in state`);
|
|
39
|
+
console.error("Run 'griffin apply' to sync your monitors first");
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
42
|
planId = stateEntry.planId;
|
|
43
43
|
}
|
|
44
44
|
// Trigger the run with environment
|
|
45
|
-
console.log(`Triggering run for
|
|
45
|
+
console.log(`Triggering run for monitor: ${planId}`);
|
|
46
46
|
console.log(`Target environment: ${options.targetEnv}`);
|
|
47
|
-
const response = await runsApi.
|
|
47
|
+
const response = await runsApi.runsTriggerMonitorIdPost(planId, {
|
|
48
48
|
environment: options.targetEnv,
|
|
49
49
|
});
|
|
50
50
|
console.log(`Run ID: ${response.data.data.id}`);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { loadState } from "../core/state.js";
|
|
2
|
-
import {
|
|
2
|
+
import { discoverMonitors, formatDiscoveryErrors } from "../core/discovery.js";
|
|
3
3
|
import { terminal } from "../utils/terminal.js";
|
|
4
4
|
/**
|
|
5
|
-
* Validate test
|
|
5
|
+
* Validate test monitor files without syncing
|
|
6
6
|
*/
|
|
7
7
|
export async function executeValidate() {
|
|
8
|
-
const spinner = terminal.spinner("Validating test
|
|
8
|
+
const spinner = terminal.spinner("Validating test monitors...").start();
|
|
9
9
|
try {
|
|
10
10
|
// Load state for discovery settings
|
|
11
11
|
const state = await loadState();
|
|
@@ -14,8 +14,8 @@ export async function executeValidate() {
|
|
|
14
14
|
"node_modules/**",
|
|
15
15
|
"dist/**",
|
|
16
16
|
];
|
|
17
|
-
// Discover
|
|
18
|
-
const {
|
|
17
|
+
// Discover monitors
|
|
18
|
+
const { monitors, errors } = await discoverMonitors(discoveryPattern, discoveryIgnore);
|
|
19
19
|
// Report errors
|
|
20
20
|
if (errors.length > 0) {
|
|
21
21
|
spinner.fail(`Validation failed with ${errors.length} error(s)`);
|
|
@@ -24,20 +24,20 @@ export async function executeValidate() {
|
|
|
24
24
|
terminal.exit(1);
|
|
25
25
|
}
|
|
26
26
|
// Report success
|
|
27
|
-
spinner.succeed(`Found ${terminal.colors.bold(
|
|
27
|
+
spinner.succeed(`Found ${terminal.colors.bold(monitors.length.toString())} valid monitor(s)`);
|
|
28
28
|
terminal.blank();
|
|
29
|
-
for (const {
|
|
29
|
+
for (const { monitor, filePath, exportName } of monitors) {
|
|
30
30
|
const shortPath = filePath.replace(process.cwd(), ".");
|
|
31
31
|
const exportInfo = exportName === "default" ? "" : terminal.colors.dim(` (${exportName})`);
|
|
32
|
-
terminal.log(` ${terminal.colors.green("●")} ${terminal.colors.cyan(
|
|
32
|
+
terminal.log(` ${terminal.colors.green("●")} ${terminal.colors.cyan(monitor.name)}${exportInfo}`);
|
|
33
33
|
terminal.dim(` ${shortPath}`);
|
|
34
|
-
terminal.dim(` Nodes: ${
|
|
35
|
-
if (
|
|
36
|
-
terminal.dim(` Schedule: Every ${
|
|
34
|
+
terminal.dim(` Nodes: ${monitor.nodes.length}, Edges: ${monitor.edges.length}`);
|
|
35
|
+
if (monitor.frequency) {
|
|
36
|
+
terminal.dim(` Schedule: Every ${monitor.frequency.every} ${monitor.frequency.unit}`);
|
|
37
37
|
}
|
|
38
38
|
terminal.blank();
|
|
39
39
|
}
|
|
40
|
-
terminal.success("All
|
|
40
|
+
terminal.success("All monitors are valid");
|
|
41
41
|
}
|
|
42
42
|
catch (error) {
|
|
43
43
|
spinner.fail("Validation failed");
|
package/dist/core/apply.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface ApplyResult {
|
|
|
7
7
|
}
|
|
8
8
|
export interface ApplyAction {
|
|
9
9
|
type: "create" | "update" | "delete";
|
|
10
|
-
|
|
10
|
+
monitorName: string;
|
|
11
11
|
success: boolean;
|
|
12
12
|
error?: string;
|
|
13
13
|
}
|
|
@@ -17,7 +17,7 @@ export interface ApplyError {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Apply diff actions to the hub.
|
|
20
|
-
* CLI injects both project and environment into
|
|
20
|
+
* CLI injects both project and environment into monitor payloads.
|
|
21
21
|
*/
|
|
22
22
|
export declare function applyDiff(diff: DiffResult, sdk: GriffinHubSdk, options?: {
|
|
23
23
|
dryRun?: boolean;
|
package/dist/core/apply.js
CHANGED
|
@@ -2,7 +2,7 @@ import { terminal } from "../utils/terminal.js";
|
|
|
2
2
|
import { withSDKErrorHandling } from "../utils/sdk-error.js";
|
|
3
3
|
/**
|
|
4
4
|
* Apply diff actions to the hub.
|
|
5
|
-
* CLI injects both project and environment into
|
|
5
|
+
* CLI injects both project and environment into monitor payloads.
|
|
6
6
|
*/
|
|
7
7
|
export async function applyDiff(diff, sdk, options) {
|
|
8
8
|
const applied = [];
|
|
@@ -16,7 +16,7 @@ export async function applyDiff(diff, sdk, options) {
|
|
|
16
16
|
for (const action of actionsToApply) {
|
|
17
17
|
try {
|
|
18
18
|
if (options?.dryRun) {
|
|
19
|
-
terminal.dim(`[DRY RUN] Would ${action.type}
|
|
19
|
+
terminal.dim(`[DRY RUN] Would ${action.type} monitor: ${action.monitor?.name || action.remoteMonitor?.name}`);
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
22
|
switch (action.type) {
|
|
@@ -39,7 +39,7 @@ export async function applyDiff(diff, sdk, options) {
|
|
|
39
39
|
});
|
|
40
40
|
applied.push({
|
|
41
41
|
type: action.type,
|
|
42
|
-
|
|
42
|
+
monitorName: action.monitor?.name || action.remoteMonitor?.name || "unknown",
|
|
43
43
|
success: false,
|
|
44
44
|
error: error.message,
|
|
45
45
|
});
|
|
@@ -55,57 +55,57 @@ export async function applyDiff(diff, sdk, options) {
|
|
|
55
55
|
* Apply a create action
|
|
56
56
|
*/
|
|
57
57
|
async function applyCreate(action, sdk, applied) {
|
|
58
|
-
const
|
|
58
|
+
const resolvedMonitor = action.monitor;
|
|
59
59
|
//const variables = await loadVariables(environment);
|
|
60
|
-
//const
|
|
61
|
-
const { data:
|
|
62
|
-
body:
|
|
60
|
+
//const resolvedMonitor = resolveMonitor(monitor, projectId, environment, variables);
|
|
61
|
+
const { data: createdMonitor } = await sdk.postMonitor({
|
|
62
|
+
body: resolvedMonitor,
|
|
63
63
|
});
|
|
64
64
|
applied.push({
|
|
65
65
|
type: "create",
|
|
66
|
-
|
|
66
|
+
monitorName: createdMonitor.data.name,
|
|
67
67
|
success: true,
|
|
68
68
|
});
|
|
69
|
-
terminal.success(`Created: ${terminal.colors.cyan(
|
|
69
|
+
terminal.success(`Created: ${terminal.colors.cyan(createdMonitor.data.name)}`);
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
72
|
* Apply an update action
|
|
73
73
|
*/
|
|
74
74
|
async function applyUpdate(action, sdk, applied) {
|
|
75
|
-
const
|
|
76
|
-
//const
|
|
75
|
+
const resolvedMonitor = action.monitor;
|
|
76
|
+
//const remoteMonitor = action.remoteMonitor!;
|
|
77
77
|
//const variables = await loadVariables(environment);
|
|
78
|
-
//const
|
|
79
|
-
// Use the remote
|
|
80
|
-
await sdk.
|
|
78
|
+
//const resolvedMonitor = resolveMonitor(monitor, projectId, environment, variables);
|
|
79
|
+
// Use the remote monitor's ID for the update
|
|
80
|
+
await sdk.putMonitorById({
|
|
81
81
|
path: {
|
|
82
|
-
id: action.
|
|
82
|
+
id: action.remoteMonitor.id,
|
|
83
83
|
},
|
|
84
|
-
body:
|
|
84
|
+
body: resolvedMonitor,
|
|
85
85
|
});
|
|
86
86
|
applied.push({
|
|
87
87
|
type: "update",
|
|
88
|
-
|
|
88
|
+
monitorName: action.remoteMonitor.name,
|
|
89
89
|
success: true,
|
|
90
90
|
});
|
|
91
|
-
terminal.success(`Updated: ${terminal.colors.cyan(action.
|
|
91
|
+
terminal.success(`Updated: ${terminal.colors.cyan(action.remoteMonitor.name)}`);
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* Apply a delete action
|
|
95
95
|
*/
|
|
96
96
|
async function applyDelete(action, sdk, applied) {
|
|
97
|
-
const
|
|
98
|
-
await withSDKErrorHandling(() => sdk.
|
|
97
|
+
const remoteMonitor = action.remoteMonitor;
|
|
98
|
+
await withSDKErrorHandling(() => sdk.deleteMonitorById({
|
|
99
99
|
path: {
|
|
100
|
-
id:
|
|
100
|
+
id: remoteMonitor.id,
|
|
101
101
|
},
|
|
102
|
-
}), `Failed to delete
|
|
102
|
+
}), `Failed to delete monitor "${remoteMonitor.name}"`);
|
|
103
103
|
applied.push({
|
|
104
104
|
type: "delete",
|
|
105
|
-
|
|
105
|
+
monitorName: remoteMonitor.name,
|
|
106
106
|
success: true,
|
|
107
107
|
});
|
|
108
|
-
terminal.success(`Deleted: ${terminal.colors.cyan(
|
|
108
|
+
terminal.success(`Deleted: ${terminal.colors.cyan(remoteMonitor.name)}`);
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
111
|
* Format apply result for display
|
|
@@ -121,7 +121,7 @@ export function formatApplyResult(result) {
|
|
|
121
121
|
for (const action of result.applied) {
|
|
122
122
|
const icon = action.success ? "✓" : "✗";
|
|
123
123
|
const status = action.success ? action.type : `${action.type} (failed)`;
|
|
124
|
-
lines.push(` ${icon} ${action.
|
|
124
|
+
lines.push(` ${icon} ${action.monitorName} - ${status}`);
|
|
125
125
|
if (action.error) {
|
|
126
126
|
lines.push(` Error: ${action.error}`);
|
|
127
127
|
}
|
package/dist/core/apply.test.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
2
|
import { applyDiff } from "./apply.js";
|
|
3
|
-
// Helper to create a minimal test
|
|
4
|
-
function
|
|
3
|
+
// Helper to create a minimal test monitor
|
|
4
|
+
function createMonitor(name) {
|
|
5
5
|
return {
|
|
6
|
-
id: `
|
|
6
|
+
id: `monitor-${name}`,
|
|
7
7
|
name,
|
|
8
8
|
project: "test-project",
|
|
9
9
|
environment: "test",
|
|
@@ -19,92 +19,92 @@ describe("applyDiff", () => {
|
|
|
19
19
|
actions: [],
|
|
20
20
|
summary: { creates: 0, updates: 0, deletes: 0, noops: 0 },
|
|
21
21
|
};
|
|
22
|
-
const
|
|
23
|
-
const result = await applyDiff(diff,
|
|
22
|
+
const mockMonitorApi = {};
|
|
23
|
+
const result = await applyDiff(diff, mockMonitorApi);
|
|
24
24
|
expect(result.success).toBe(true);
|
|
25
25
|
expect(result.applied).toHaveLength(0);
|
|
26
26
|
expect(result.errors).toHaveLength(0);
|
|
27
27
|
});
|
|
28
28
|
it("should skip noop actions", async () => {
|
|
29
|
-
const
|
|
29
|
+
const monitor = createMonitor("health-check");
|
|
30
30
|
const diff = {
|
|
31
31
|
actions: [
|
|
32
32
|
{
|
|
33
33
|
type: "noop",
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
monitor: monitor,
|
|
35
|
+
remoteMonitor: monitor,
|
|
36
36
|
reason: "unchanged",
|
|
37
37
|
},
|
|
38
38
|
],
|
|
39
39
|
summary: { creates: 0, updates: 0, deletes: 0, noops: 1 },
|
|
40
40
|
};
|
|
41
|
-
const
|
|
42
|
-
const result = await applyDiff(diff,
|
|
41
|
+
const mockMonitorApi = {};
|
|
42
|
+
const result = await applyDiff(diff, mockMonitorApi);
|
|
43
43
|
expect(result.success).toBe(true);
|
|
44
44
|
expect(result.applied).toHaveLength(0);
|
|
45
45
|
});
|
|
46
46
|
it("should apply create action", async () => {
|
|
47
|
-
const
|
|
47
|
+
const monitor = createMonitor("new-monitor");
|
|
48
48
|
const diff = {
|
|
49
49
|
actions: [
|
|
50
50
|
{
|
|
51
51
|
type: "create",
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
monitor: monitor,
|
|
53
|
+
remoteMonitor: null,
|
|
54
54
|
reason: "new",
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
57
|
summary: { creates: 1, updates: 0, deletes: 0, noops: 0 },
|
|
58
58
|
};
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
data: { data: { ...
|
|
59
|
+
const mockMonitorApi = {
|
|
60
|
+
postMonitor: vi.fn().mockResolvedValue({
|
|
61
|
+
data: { data: { ...monitor, id: "created-id" } },
|
|
62
62
|
}),
|
|
63
63
|
};
|
|
64
|
-
const result = await applyDiff(diff,
|
|
64
|
+
const result = await applyDiff(diff, mockMonitorApi);
|
|
65
65
|
expect(result.success).toBe(true);
|
|
66
66
|
expect(result.applied).toHaveLength(1);
|
|
67
67
|
expect(result.applied[0].type).toBe("create");
|
|
68
|
-
expect(result.applied[0].
|
|
68
|
+
expect(result.applied[0].monitorName).toBe("new-monitor");
|
|
69
69
|
expect(result.applied[0].success).toBe(true);
|
|
70
70
|
// Verify the API was called with injected project and environment
|
|
71
|
-
expect(
|
|
71
|
+
expect(mockMonitorApi.postMonitor).toHaveBeenCalledWith(expect.objectContaining({
|
|
72
72
|
body: expect.objectContaining({
|
|
73
|
-
name: "new-
|
|
73
|
+
name: "new-monitor",
|
|
74
74
|
project: "test-project",
|
|
75
75
|
environment: "test",
|
|
76
76
|
}),
|
|
77
77
|
}));
|
|
78
78
|
});
|
|
79
79
|
it("should apply update action", async () => {
|
|
80
|
-
const
|
|
81
|
-
const
|
|
80
|
+
const localMonitor = createMonitor("existing-monitor");
|
|
81
|
+
const remoteMonitor = { ...localMonitor, id: "remote-id" };
|
|
82
82
|
const diff = {
|
|
83
83
|
actions: [
|
|
84
84
|
{
|
|
85
85
|
type: "update",
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
monitor: localMonitor,
|
|
87
|
+
remoteMonitor: remoteMonitor,
|
|
88
88
|
reason: "changed",
|
|
89
89
|
},
|
|
90
90
|
],
|
|
91
91
|
summary: { creates: 0, updates: 1, deletes: 0, noops: 0 },
|
|
92
92
|
};
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
data: { data:
|
|
93
|
+
const mockMonitorApi = {
|
|
94
|
+
putMonitorById: vi.fn().mockResolvedValue({
|
|
95
|
+
data: { data: remoteMonitor },
|
|
96
96
|
}),
|
|
97
97
|
};
|
|
98
|
-
const result = await applyDiff(diff,
|
|
98
|
+
const result = await applyDiff(diff, mockMonitorApi);
|
|
99
99
|
expect(result.success).toBe(true);
|
|
100
100
|
expect(result.applied).toHaveLength(1);
|
|
101
101
|
expect(result.applied[0].type).toBe("update");
|
|
102
|
-
expect(result.applied[0].
|
|
102
|
+
expect(result.applied[0].monitorName).toBe("existing-monitor");
|
|
103
103
|
expect(result.applied[0].success).toBe(true);
|
|
104
|
-
// Verify the API was called with the remote
|
|
105
|
-
expect(
|
|
104
|
+
// Verify the API was called with the remote monitor's ID
|
|
105
|
+
expect(mockMonitorApi.putMonitorById).toHaveBeenCalledWith(expect.objectContaining({
|
|
106
106
|
body: expect.objectContaining({
|
|
107
|
-
name: "existing-
|
|
107
|
+
name: "existing-monitor",
|
|
108
108
|
project: "test-project",
|
|
109
109
|
environment: "test",
|
|
110
110
|
}),
|
|
@@ -114,44 +114,44 @@ describe("applyDiff", () => {
|
|
|
114
114
|
}));
|
|
115
115
|
});
|
|
116
116
|
it("should apply delete action", async () => {
|
|
117
|
-
const
|
|
117
|
+
const remoteMonitor = createMonitor("old-monitor");
|
|
118
118
|
const diff = {
|
|
119
|
-
actions: [{ type: "delete",
|
|
119
|
+
actions: [{ type: "delete", monitor: null, remoteMonitor, reason: "removed" }],
|
|
120
120
|
summary: { creates: 0, updates: 0, deletes: 1, noops: 0 },
|
|
121
121
|
};
|
|
122
|
-
const
|
|
123
|
-
|
|
122
|
+
const mockMonitorApi = {
|
|
123
|
+
deleteMonitorById: vi.fn().mockResolvedValue({}),
|
|
124
124
|
};
|
|
125
|
-
const result = await applyDiff(diff,
|
|
125
|
+
const result = await applyDiff(diff, mockMonitorApi);
|
|
126
126
|
expect(result.success).toBe(true);
|
|
127
127
|
expect(result.applied).toHaveLength(1);
|
|
128
128
|
expect(result.applied[0].type).toBe("delete");
|
|
129
|
-
expect(result.applied[0].
|
|
129
|
+
expect(result.applied[0].monitorName).toBe("old-monitor");
|
|
130
130
|
expect(result.applied[0].success).toBe(true);
|
|
131
|
-
// Verify the API was called with the remote
|
|
132
|
-
expect(
|
|
131
|
+
// Verify the API was called with the remote monitor's ID
|
|
132
|
+
expect(mockMonitorApi.deleteMonitorById).toHaveBeenCalledWith({
|
|
133
133
|
path: {
|
|
134
|
-
id:
|
|
134
|
+
id: remoteMonitor.id,
|
|
135
135
|
},
|
|
136
136
|
});
|
|
137
137
|
});
|
|
138
138
|
it("should handle errors gracefully", async () => {
|
|
139
|
-
const
|
|
139
|
+
const monitor = createMonitor("failing-monitor");
|
|
140
140
|
const diff = {
|
|
141
141
|
actions: [
|
|
142
142
|
{
|
|
143
143
|
type: "create",
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
monitor: monitor,
|
|
145
|
+
remoteMonitor: null,
|
|
146
146
|
reason: "new",
|
|
147
147
|
},
|
|
148
148
|
],
|
|
149
149
|
summary: { creates: 1, updates: 0, deletes: 0, noops: 0 },
|
|
150
150
|
};
|
|
151
|
-
const
|
|
152
|
-
|
|
151
|
+
const mockMonitorApi = {
|
|
152
|
+
postMonitor: vi.fn().mockRejectedValue(new Error("API Error")),
|
|
153
153
|
};
|
|
154
|
-
const result = await applyDiff(diff,
|
|
154
|
+
const result = await applyDiff(diff, mockMonitorApi);
|
|
155
155
|
expect(result.success).toBe(false);
|
|
156
156
|
expect(result.errors).toHaveLength(1);
|
|
157
157
|
expect(result.applied).toHaveLength(1);
|
|
@@ -159,26 +159,26 @@ describe("applyDiff", () => {
|
|
|
159
159
|
expect(result.applied[0].error).toBe("API Error");
|
|
160
160
|
});
|
|
161
161
|
it("should skip actions in dry-run mode", async () => {
|
|
162
|
-
const
|
|
162
|
+
const monitor = createMonitor("dry-run-monitor");
|
|
163
163
|
const diff = {
|
|
164
164
|
actions: [
|
|
165
165
|
{
|
|
166
166
|
type: "create",
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
monitor: monitor,
|
|
168
|
+
remoteMonitor: null,
|
|
169
169
|
reason: "new",
|
|
170
170
|
},
|
|
171
171
|
],
|
|
172
172
|
summary: { creates: 1, updates: 0, deletes: 0, noops: 0 },
|
|
173
173
|
};
|
|
174
|
-
const
|
|
175
|
-
|
|
174
|
+
const mockMonitorApi = {
|
|
175
|
+
postMonitor: vi.fn(),
|
|
176
176
|
};
|
|
177
|
-
const result = await applyDiff(diff,
|
|
177
|
+
const result = await applyDiff(diff, mockMonitorApi, {
|
|
178
178
|
dryRun: true,
|
|
179
179
|
});
|
|
180
180
|
expect(result.success).toBe(true);
|
|
181
181
|
expect(result.applied).toHaveLength(0);
|
|
182
|
-
expect(
|
|
182
|
+
expect(mockMonitorApi.postMonitor).not.toHaveBeenCalled();
|
|
183
183
|
});
|
|
184
184
|
});
|
package/dist/core/diff.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { type
|
|
1
|
+
import type { MonitorV1 } from "@griffin-app/griffin-hub-sdk";
|
|
2
|
+
import { type MonitorChanges } from "./monitor-diff.js";
|
|
3
3
|
export type DiffActionType = "create" | "update" | "delete" | "noop";
|
|
4
|
-
export type
|
|
4
|
+
export type ResolvedMonitor = Omit<MonitorV1, "id">;
|
|
5
5
|
export interface DiffAction {
|
|
6
6
|
type: DiffActionType;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
monitor: ResolvedMonitor | null;
|
|
8
|
+
remoteMonitor: MonitorV1 | null;
|
|
9
9
|
reason: string;
|
|
10
|
-
changes?:
|
|
10
|
+
changes?: MonitorChanges;
|
|
11
11
|
}
|
|
12
12
|
export interface DiffResult {
|
|
13
13
|
actions: DiffAction[];
|
|
@@ -22,17 +22,17 @@ export interface DiffOptions {
|
|
|
22
22
|
includeDeletions: boolean;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Compute diff between local
|
|
26
|
-
* Local
|
|
27
|
-
*
|
|
25
|
+
* Compute diff between local monitors and remote monitors (hub is source of truth).
|
|
26
|
+
* Local monitors should be resolved (variables replaced with actual values) before calling this.
|
|
27
|
+
* Monitors are matched by name.
|
|
28
28
|
*
|
|
29
29
|
* Rules:
|
|
30
|
-
* - CREATE:
|
|
31
|
-
* - UPDATE:
|
|
32
|
-
* - DELETE:
|
|
33
|
-
* - NOOP:
|
|
30
|
+
* - CREATE: Monitor exists locally but not on hub
|
|
31
|
+
* - UPDATE: Monitor exists in both, but content differs
|
|
32
|
+
* - DELETE: Monitor exists on hub but not locally (only if includeDeletions is true)
|
|
33
|
+
* - NOOP: Monitor exists in both with same content
|
|
34
34
|
*/
|
|
35
|
-
export declare function computeDiff(
|
|
35
|
+
export declare function computeDiff(localMonitors: ResolvedMonitor[], remoteMonitors: MonitorV1[], options: DiffOptions): DiffResult;
|
|
36
36
|
/**
|
|
37
37
|
* Format diff result as human-readable text with granular changes
|
|
38
38
|
*/
|