@griffin-app/griffin-cli 1.0.11 → 1.0.13
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 +85 -36
- package/dist/commands/env.d.ts +8 -0
- package/dist/commands/env.js +31 -1
- package/dist/commands/hub/integrations.d.ts +8 -6
- package/dist/commands/hub/integrations.js +155 -102
- package/dist/commands/hub/notifications.d.ts +8 -2
- package/dist/commands/hub/notifications.js +116 -7
- package/dist/commands/init.js +7 -29
- package/dist/commands/variables.d.ts +12 -0
- package/dist/commands/variables.js +87 -0
- package/dist/core/apply.test.js +1 -0
- package/dist/core/diff.test.js +2 -0
- package/dist/core/discovery.d.ts +1 -0
- package/dist/core/discovery.js +14 -4
- package/dist/core/monitor-diff.js +15 -0
- package/dist/core/variables.d.ts +2 -2
- package/dist/core/variables.js +6 -24
- package/dist/providers/registry.d.ts +24 -0
- package/dist/providers/registry.js +123 -0
- package/dist/schemas/state.d.ts +15 -5
- package/dist/schemas/state.js +3 -1
- package/dist/utils/terminal.d.ts +4 -0
- package/dist/utils/terminal.js +11 -0
- package/package.json +4 -4
|
@@ -2,6 +2,14 @@ import { loadState } from "../../core/state.js";
|
|
|
2
2
|
import { createSdkWithCredentials } from "../../core/sdk.js";
|
|
3
3
|
import { terminal } from "../../utils/terminal.js";
|
|
4
4
|
import { withSDKErrorHandling } from "../../utils/sdk-error.js";
|
|
5
|
+
function orExit(value, errorMessage) {
|
|
6
|
+
if (value === undefined) {
|
|
7
|
+
terminal.error(errorMessage);
|
|
8
|
+
terminal.exit(1);
|
|
9
|
+
throw new Error(errorMessage);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* List notification rules (read-only).
|
|
7
15
|
* Rules are defined in monitor DSL and synced via `griffin hub apply`.
|
|
@@ -55,8 +63,102 @@ export async function executeNotificationsList(options) {
|
|
|
55
63
|
terminal.exit(1);
|
|
56
64
|
}
|
|
57
65
|
}
|
|
66
|
+
async function fetchNotificationIntegrations(sdk) {
|
|
67
|
+
const result = await withSDKErrorHandling(() => sdk.getIntegrations({
|
|
68
|
+
query: { category: "notifications", enabled: true },
|
|
69
|
+
}), "Failed to fetch integrations");
|
|
70
|
+
const list = result?.data?.data ?? [];
|
|
71
|
+
return list
|
|
72
|
+
.filter((i) => i.category === "notifications")
|
|
73
|
+
.map((i) => ({
|
|
74
|
+
id: i.id,
|
|
75
|
+
name: i.name,
|
|
76
|
+
provider: i.provider,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
function promptRoutingForProvider(provider) {
|
|
80
|
+
switch (provider) {
|
|
81
|
+
case "slack": {
|
|
82
|
+
return terminal
|
|
83
|
+
.input("Slack channel (e.g. #alerts)", "#alerts")
|
|
84
|
+
.then((value) => ({
|
|
85
|
+
provider: "slack",
|
|
86
|
+
channel: (value ?? "").trim() || "#alerts",
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
case "email": {
|
|
90
|
+
return terminal.input("To addresses (comma-separated)").then((value) => {
|
|
91
|
+
const toAddresses = (value ?? "")
|
|
92
|
+
.split(",")
|
|
93
|
+
.map((s) => s.trim())
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
if (toAddresses.length === 0) {
|
|
96
|
+
terminal.error("At least one email address is required.");
|
|
97
|
+
terminal.exit(1);
|
|
98
|
+
}
|
|
99
|
+
return { provider: "email", toAddresses };
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
default:
|
|
103
|
+
return Promise.resolve({ provider: "webhook" });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
58
106
|
/**
|
|
59
|
-
*
|
|
107
|
+
* Build routing object from CLI options. Exactly one of channel, toAddresses, or webhook (none) must be used.
|
|
108
|
+
*/
|
|
109
|
+
function buildRoutingFromOptions(options) {
|
|
110
|
+
if (options.channel !== undefined && options.channel !== "") {
|
|
111
|
+
return { provider: "slack", channel: options.channel };
|
|
112
|
+
}
|
|
113
|
+
if (options.toAddresses !== undefined && options.toAddresses !== "") {
|
|
114
|
+
const toAddresses = options.toAddresses
|
|
115
|
+
.split(",")
|
|
116
|
+
.map((s) => s.trim())
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
if (toAddresses.length > 0) {
|
|
119
|
+
return { provider: "email", toAddresses };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { provider: "webhook" };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Run interactive wizard: select integration, then prompt for provider-specific options (channel, to-addresses, or none).
|
|
126
|
+
*/
|
|
127
|
+
async function executeNotificationsTestWizard(sdk, options) {
|
|
128
|
+
const integrations = await fetchNotificationIntegrations(sdk);
|
|
129
|
+
if (integrations.length === 0) {
|
|
130
|
+
terminal.error("No notification integrations found.");
|
|
131
|
+
terminal.dim("Connect one with: griffin hub integrations connect notifications <provider>");
|
|
132
|
+
terminal.exit(1);
|
|
133
|
+
}
|
|
134
|
+
if (options.integration) {
|
|
135
|
+
const match = orExit(integrations.find((i) => i.id === options.integration ||
|
|
136
|
+
i.name.toLowerCase() === (options.integration ?? "").toLowerCase()), `Integration not found: ${options.integration}`);
|
|
137
|
+
const routing = await promptRoutingForProvider(match.provider);
|
|
138
|
+
return { integration: match.id, routing };
|
|
139
|
+
}
|
|
140
|
+
const choiceLabels = integrations.map((i) => `${i.name} (${i.provider})`);
|
|
141
|
+
const choice = await terminal.select("Select integration to test", choiceLabels);
|
|
142
|
+
const found = orExit(integrations.find((i) => `${i.name} (${i.provider})` === choice), "No integration selected.");
|
|
143
|
+
const routing = await promptRoutingForProvider(found.provider);
|
|
144
|
+
return { integration: found.id, routing };
|
|
145
|
+
}
|
|
146
|
+
function canBuildRoutingFromOptions(options) {
|
|
147
|
+
if (options.channel !== undefined && options.channel !== "")
|
|
148
|
+
return true;
|
|
149
|
+
if (options.toAddresses !== undefined && options.toAddresses !== "") {
|
|
150
|
+
const toAddresses = options.toAddresses
|
|
151
|
+
.split(",")
|
|
152
|
+
.map((s) => s.trim())
|
|
153
|
+
.filter(Boolean);
|
|
154
|
+
if (toAddresses.length > 0)
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Test a notification integration. Without options, runs a wizard (select integration, then provider-specific prompts).
|
|
161
|
+
* With --integration and routing flags, runs non-interactively.
|
|
60
162
|
*/
|
|
61
163
|
export async function executeNotificationsTest(options) {
|
|
62
164
|
try {
|
|
@@ -68,14 +170,21 @@ export async function executeNotificationsTest(options) {
|
|
|
68
170
|
terminal.exit(1);
|
|
69
171
|
}
|
|
70
172
|
const sdk = await createSdkWithCredentials(state.hub.baseUrl);
|
|
173
|
+
const useWizard = !options.integration || !canBuildRoutingFromOptions(options);
|
|
174
|
+
let integration;
|
|
175
|
+
let routing;
|
|
176
|
+
if (useWizard) {
|
|
177
|
+
const wizardResult = await executeNotificationsTestWizard(sdk, options);
|
|
178
|
+
integration = wizardResult.integration;
|
|
179
|
+
routing = wizardResult.routing;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
integration = options.integration;
|
|
183
|
+
routing = buildRoutingFromOptions(options);
|
|
184
|
+
}
|
|
71
185
|
const spinner = terminal.spinner("Sending test notification...").start();
|
|
72
186
|
const response = await withSDKErrorHandling(() => sdk.postNotificationsTest({
|
|
73
|
-
body: {
|
|
74
|
-
channel: {
|
|
75
|
-
type: "webhook",
|
|
76
|
-
url: options.webhook,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
187
|
+
body: { integration, routing },
|
|
79
188
|
}), "Failed to send test notification");
|
|
80
189
|
spinner.stop();
|
|
81
190
|
const result = response?.data?.data;
|
package/dist/commands/init.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { initState, stateExists, getStateFilePath, } from "../core/state.js";
|
|
1
|
+
import { initState, stateExists, getStateFilePath } from "../core/state.js";
|
|
4
2
|
import { detectProjectId } from "../core/project.js";
|
|
5
3
|
import { terminal } from "../utils/terminal.js";
|
|
6
|
-
const VARIABLES_FILE = "variables.yaml";
|
|
7
|
-
const VARIABLES_TEMPLATE = `# Per-environment variables for test monitors. Reference in monitors with variable("key").
|
|
8
|
-
# Edit values below for each environment.
|
|
9
|
-
environments:
|
|
10
|
-
dev:
|
|
11
|
-
# api_key: "your-api-key"
|
|
12
|
-
staging:
|
|
13
|
-
# api_key: "your-staging-key"
|
|
14
|
-
production:
|
|
15
|
-
# api_key: "your-production-key"
|
|
16
|
-
`;
|
|
17
4
|
/**
|
|
18
5
|
* Initialize griffin in the current directory
|
|
19
6
|
*/
|
|
@@ -31,30 +18,21 @@ export async function executeInit(options) {
|
|
|
31
18
|
projectId = await detectProjectId();
|
|
32
19
|
}
|
|
33
20
|
spinner.succeed(`Project: ${terminal.colors.cyan(projectId)}`);
|
|
34
|
-
// Initialize state file
|
|
21
|
+
// Initialize state file (includes default environment)
|
|
35
22
|
await initState(projectId);
|
|
36
23
|
terminal.success(`Created state file: ${terminal.colors.dim(getStateFilePath())}`);
|
|
37
|
-
// Create variables.yaml if it doesn't exist
|
|
38
|
-
const variablesPath = path.join(process.cwd(), VARIABLES_FILE);
|
|
39
|
-
try {
|
|
40
|
-
await fs.access(variablesPath);
|
|
41
|
-
terminal.dim(`variables.yaml already exists, skipping`);
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
await fs.writeFile(variablesPath, VARIABLES_TEMPLATE, "utf-8");
|
|
45
|
-
terminal.success(`Created ${terminal.colors.dim(VARIABLES_FILE)}`);
|
|
46
|
-
}
|
|
47
24
|
terminal.blank();
|
|
48
25
|
terminal.success("Initialization complete!");
|
|
49
26
|
terminal.blank();
|
|
50
27
|
terminal.info("Next steps:");
|
|
51
|
-
terminal.dim(" 1.
|
|
28
|
+
terminal.dim(" 1. Add variables for your environment (optional):");
|
|
29
|
+
terminal.dim(" griffin variables add API_KEY=your-key --env default");
|
|
52
30
|
terminal.dim(" 2. Create test monitors (*.ts files in __griffin__/ directories)");
|
|
53
|
-
terminal.dim(" 3. Run tests locally
|
|
54
|
-
terminal.dim(" griffin local run
|
|
31
|
+
terminal.dim(" 3. Run tests locally:");
|
|
32
|
+
terminal.dim(" griffin local run");
|
|
55
33
|
terminal.dim(" 4. Connect to hub (optional):");
|
|
56
34
|
terminal.dim(" griffin hub connect --url <url> --token <token>");
|
|
57
35
|
terminal.dim(" 5. Deploy to hub:");
|
|
58
|
-
terminal.dim(" griffin hub apply
|
|
36
|
+
terminal.dim(" griffin hub apply");
|
|
59
37
|
terminal.blank();
|
|
60
38
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List all variables for an environment
|
|
3
|
+
*/
|
|
4
|
+
export declare function executeVariablesList(env: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Add or update a variable for an environment
|
|
7
|
+
*/
|
|
8
|
+
export declare function executeVariablesAdd(keyValue: string, env: string): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Remove a variable from an environment
|
|
11
|
+
*/
|
|
12
|
+
export declare function executeVariablesRemove(key: string, env: string): Promise<void>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { loadState, saveState, getEnvironment } from "../core/state.js";
|
|
2
|
+
import { terminal } from "../utils/terminal.js";
|
|
3
|
+
/**
|
|
4
|
+
* List all variables for an environment
|
|
5
|
+
*/
|
|
6
|
+
export async function executeVariablesList(env) {
|
|
7
|
+
try {
|
|
8
|
+
const state = await loadState();
|
|
9
|
+
const envConfig = await getEnvironment(env);
|
|
10
|
+
if (!envConfig.variables || Object.keys(envConfig.variables).length === 0) {
|
|
11
|
+
terminal.warn(`No variables defined for environment '${env}'.`);
|
|
12
|
+
terminal.blank();
|
|
13
|
+
terminal.dim(`Add variables with: griffin variables add KEY=VALUE --env ${env}`);
|
|
14
|
+
terminal.blank();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
terminal.info(`Variables for environment '${env}':`);
|
|
18
|
+
terminal.blank();
|
|
19
|
+
for (const [key, value] of Object.entries(envConfig.variables)) {
|
|
20
|
+
const keyDisplay = terminal.colors.cyan(key);
|
|
21
|
+
const valueDisplay = terminal.colors.dim(value);
|
|
22
|
+
terminal.log(` ${keyDisplay} = ${valueDisplay}`);
|
|
23
|
+
}
|
|
24
|
+
terminal.blank();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
terminal.error(error.message);
|
|
28
|
+
terminal.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Add or update a variable for an environment
|
|
33
|
+
*/
|
|
34
|
+
export async function executeVariablesAdd(keyValue, env) {
|
|
35
|
+
try {
|
|
36
|
+
// Parse KEY=VALUE format
|
|
37
|
+
const match = keyValue.match(/^([^=]+)=(.*)$/);
|
|
38
|
+
if (!match) {
|
|
39
|
+
throw new Error("Invalid format. Use: griffin variables add KEY=VALUE --env <env>");
|
|
40
|
+
}
|
|
41
|
+
const [, key, value] = match;
|
|
42
|
+
const trimmedKey = key.trim();
|
|
43
|
+
const trimmedValue = value.trim();
|
|
44
|
+
if (!trimmedKey) {
|
|
45
|
+
throw new Error("Variable key cannot be empty");
|
|
46
|
+
}
|
|
47
|
+
const state = await loadState();
|
|
48
|
+
const envConfig = await getEnvironment(env);
|
|
49
|
+
// Initialize variables object if it doesn't exist
|
|
50
|
+
if (!envConfig.variables) {
|
|
51
|
+
envConfig.variables = {};
|
|
52
|
+
}
|
|
53
|
+
// Add or update the variable
|
|
54
|
+
envConfig.variables[trimmedKey] = trimmedValue;
|
|
55
|
+
// Update the environment in state
|
|
56
|
+
state.environments[env] = envConfig;
|
|
57
|
+
await saveState(state);
|
|
58
|
+
terminal.success(`Variable '${trimmedKey}' set for environment '${env}'.`);
|
|
59
|
+
terminal.blank();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
terminal.error(error.message);
|
|
63
|
+
terminal.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Remove a variable from an environment
|
|
68
|
+
*/
|
|
69
|
+
export async function executeVariablesRemove(key, env) {
|
|
70
|
+
try {
|
|
71
|
+
const state = await loadState();
|
|
72
|
+
const envConfig = await getEnvironment(env);
|
|
73
|
+
if (!envConfig.variables || !(key in envConfig.variables)) {
|
|
74
|
+
throw new Error(`Variable '${key}' not found in environment '${env}'`);
|
|
75
|
+
}
|
|
76
|
+
delete envConfig.variables[key];
|
|
77
|
+
// Update the environment in state
|
|
78
|
+
state.environments[env] = envConfig;
|
|
79
|
+
await saveState(state);
|
|
80
|
+
terminal.success(`Variable '${key}' removed from environment '${env}'.`);
|
|
81
|
+
terminal.blank();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
terminal.error(error.message);
|
|
85
|
+
terminal.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist/core/apply.test.js
CHANGED
package/dist/core/diff.test.js
CHANGED
|
@@ -11,6 +11,7 @@ function createMonitor(name, overrides) {
|
|
|
11
11
|
frequency: { every: 5, unit: "MINUTE" },
|
|
12
12
|
nodes: [],
|
|
13
13
|
edges: [],
|
|
14
|
+
notifications: [],
|
|
14
15
|
...overrides,
|
|
15
16
|
};
|
|
16
17
|
}
|
|
@@ -24,6 +25,7 @@ function createResolvedMonitor(name, overrides) {
|
|
|
24
25
|
frequency: { every: 5, unit: "MINUTE" },
|
|
25
26
|
nodes: [],
|
|
26
27
|
edges: [],
|
|
28
|
+
notifications: [],
|
|
27
29
|
...overrides,
|
|
28
30
|
};
|
|
29
31
|
}
|
package/dist/core/discovery.d.ts
CHANGED
package/dist/core/discovery.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "tsx";
|
|
1
2
|
import { glob } from "glob";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -45,16 +46,25 @@ async function loadMonitorsFromFile(filePath) {
|
|
|
45
46
|
const module = await import(fileUrl);
|
|
46
47
|
// Check default export
|
|
47
48
|
if (module.default) {
|
|
48
|
-
|
|
49
|
+
// Handle potential double-wrapping from tsx/esModuleInterop
|
|
50
|
+
// When tsx transpiles TS files with esModuleInterop, it may wrap the default export
|
|
51
|
+
let defaultExport = module.default;
|
|
52
|
+
if (typeof defaultExport === "object" &&
|
|
53
|
+
defaultExport !== null &&
|
|
54
|
+
"default" in defaultExport &&
|
|
55
|
+
typeof defaultExport.default === "object") {
|
|
56
|
+
defaultExport = defaultExport.default;
|
|
57
|
+
}
|
|
58
|
+
if (isMonitor(defaultExport)) {
|
|
49
59
|
monitors.push({
|
|
50
|
-
monitor:
|
|
60
|
+
monitor: defaultExport,
|
|
51
61
|
filePath,
|
|
52
62
|
exportName: "default",
|
|
53
63
|
});
|
|
54
64
|
}
|
|
55
65
|
else {
|
|
56
|
-
const errors = Value.Errors(MonitorDSLSchema,
|
|
57
|
-
throw new Error(`Default export is not a valid TestMonitor. Got: ${JSON.stringify(errors, null, 2)}`);
|
|
66
|
+
const errors = Value.Errors(MonitorDSLSchema, defaultExport);
|
|
67
|
+
throw new Error(`Default export is not a valid TestMonitor. Got: ${JSON.stringify([...errors], null, 2)}`);
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
70
|
if (monitors.length === 0) {
|
|
@@ -247,6 +247,21 @@ function compareTopLevel(local, remote) {
|
|
|
247
247
|
newValue: localLocations,
|
|
248
248
|
});
|
|
249
249
|
}
|
|
250
|
+
// Compare notifications (normalize empty array to undefined)
|
|
251
|
+
const localNotifications = local.notifications && local.notifications.length > 0
|
|
252
|
+
? local.notifications
|
|
253
|
+
: undefined;
|
|
254
|
+
const remoteNotifications = remote.notifications && remote.notifications.length > 0
|
|
255
|
+
? remote.notifications
|
|
256
|
+
: undefined;
|
|
257
|
+
console.log("REMOTE NOTIFICATIONS", remoteNotifications);
|
|
258
|
+
if (!deepEqual(localNotifications, remoteNotifications)) {
|
|
259
|
+
changes.push({
|
|
260
|
+
field: "notifications",
|
|
261
|
+
oldValue: remoteNotifications,
|
|
262
|
+
newValue: localNotifications,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
250
265
|
return changes;
|
|
251
266
|
}
|
|
252
267
|
/**
|
package/dist/core/variables.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Load variables from
|
|
2
|
+
* Load variables from state file for a specific environment.
|
|
3
3
|
*
|
|
4
4
|
* @param envName - The environment name to load variables for
|
|
5
5
|
* @returns Record of variable key-value pairs
|
|
6
|
-
* @throws Error if
|
|
6
|
+
* @throws Error if environment not found or has no variables
|
|
7
7
|
*/
|
|
8
8
|
export declare function loadVariables(envName: string): Promise<Record<string, string>>;
|
|
9
9
|
/**
|
package/dist/core/variables.js
CHANGED
|
@@ -1,33 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { parse } from "yaml";
|
|
1
|
+
import { getEnvironment } from "./state.js";
|
|
4
2
|
/**
|
|
5
|
-
* Load variables from
|
|
3
|
+
* Load variables from state file for a specific environment.
|
|
6
4
|
*
|
|
7
5
|
* @param envName - The environment name to load variables for
|
|
8
6
|
* @returns Record of variable key-value pairs
|
|
9
|
-
* @throws Error if
|
|
7
|
+
* @throws Error if environment not found or has no variables
|
|
10
8
|
*/
|
|
11
9
|
export async function loadVariables(envName) {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const config = parse(content);
|
|
16
|
-
if (!config.environments) {
|
|
17
|
-
throw new Error(`Invalid variables.yaml: missing "environments" key.\nExpected format:\nenvironments:\n ${envName}:\n key: value`);
|
|
18
|
-
}
|
|
19
|
-
if (!config.environments[envName]) {
|
|
20
|
-
const availableEnvs = Object.keys(config.environments);
|
|
21
|
-
throw new Error(`Environment "${envName}" not found in variables.yaml.\nAvailable environments: ${availableEnvs.join(", ")}`);
|
|
22
|
-
}
|
|
23
|
-
return config.environments[envName];
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
if (error.code === "ENOENT") {
|
|
27
|
-
throw new Error(`variables.yaml not found in ${process.cwd()}.\nCreate one with:\nenvironments:\n ${envName}:\n my-variable: my-value`);
|
|
28
|
-
}
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
10
|
+
const envConfig = await getEnvironment(envName);
|
|
11
|
+
// Return empty object if no variables defined (allows monitors with no variable refs to work)
|
|
12
|
+
return envConfig.variables || {};
|
|
31
13
|
}
|
|
32
14
|
/**
|
|
33
15
|
* Check if a value is a variable reference.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardcoded provider registry for integration connect.
|
|
3
|
+
* Defines categories, providers, auth methods, and required fields.
|
|
4
|
+
*/
|
|
5
|
+
export type AuthMethod = "oauth" | "credentials";
|
|
6
|
+
export interface ProviderField {
|
|
7
|
+
key: string;
|
|
8
|
+
label: string;
|
|
9
|
+
secret?: boolean;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
default?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ProviderDefinition {
|
|
14
|
+
category: string;
|
|
15
|
+
provider: string;
|
|
16
|
+
displayName: string;
|
|
17
|
+
authMethod: AuthMethod;
|
|
18
|
+
configFields?: ProviderField[];
|
|
19
|
+
credentialFields?: ProviderField[];
|
|
20
|
+
}
|
|
21
|
+
export declare function getCategories(): string[];
|
|
22
|
+
export declare function getProviders(category: string): ProviderDefinition[];
|
|
23
|
+
export declare function getProvider(category: string, provider: string): ProviderDefinition | null;
|
|
24
|
+
export declare function getAllProvidersByCategory(): Map<string, ProviderDefinition[]>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardcoded provider registry for integration connect.
|
|
3
|
+
* Defines categories, providers, auth methods, and required fields.
|
|
4
|
+
*/
|
|
5
|
+
const providers = [
|
|
6
|
+
// Notifications - OAuth
|
|
7
|
+
{
|
|
8
|
+
category: "notifications",
|
|
9
|
+
provider: "slack",
|
|
10
|
+
displayName: "Slack",
|
|
11
|
+
authMethod: "oauth",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
category: "notifications",
|
|
15
|
+
provider: "pagerduty",
|
|
16
|
+
displayName: "PagerDuty",
|
|
17
|
+
authMethod: "oauth",
|
|
18
|
+
},
|
|
19
|
+
// Notifications - Credentials
|
|
20
|
+
{
|
|
21
|
+
category: "notifications",
|
|
22
|
+
provider: "webhook",
|
|
23
|
+
displayName: "Webhook",
|
|
24
|
+
authMethod: "credentials",
|
|
25
|
+
configFields: [{ key: "url", label: "Webhook URL", required: true }],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
category: "notifications",
|
|
29
|
+
provider: "slack_webhook",
|
|
30
|
+
displayName: "Slack Webhook",
|
|
31
|
+
authMethod: "credentials",
|
|
32
|
+
credentialFields: [
|
|
33
|
+
{
|
|
34
|
+
key: "webhook_url",
|
|
35
|
+
label: "Webhook URL",
|
|
36
|
+
secret: true,
|
|
37
|
+
required: true,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
category: "notifications",
|
|
43
|
+
provider: "email",
|
|
44
|
+
displayName: "Email (SES)",
|
|
45
|
+
authMethod: "credentials",
|
|
46
|
+
configFields: [
|
|
47
|
+
{ key: "fromAddress", label: "From Address", required: true },
|
|
48
|
+
{ key: "sesRegion", label: "SES Region", required: true },
|
|
49
|
+
{
|
|
50
|
+
key: "toAddresses",
|
|
51
|
+
label: "To Addresses (comma-separated)",
|
|
52
|
+
required: true,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
// Secrets - Credentials
|
|
57
|
+
{
|
|
58
|
+
category: "secrets",
|
|
59
|
+
provider: "env",
|
|
60
|
+
displayName: "Environment Variables",
|
|
61
|
+
authMethod: "credentials",
|
|
62
|
+
configFields: [{ key: "prefix", label: "Env Prefix", required: false }],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
category: "secrets",
|
|
66
|
+
provider: "aws",
|
|
67
|
+
displayName: "AWS Secrets Manager",
|
|
68
|
+
authMethod: "credentials",
|
|
69
|
+
configFields: [{ key: "region", label: "AWS Region", required: true }],
|
|
70
|
+
credentialFields: [
|
|
71
|
+
{ key: "accessKeyId", label: "Access Key ID", secret: true },
|
|
72
|
+
{ key: "secretAccessKey", label: "Secret Access Key", secret: true },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
category: "secrets",
|
|
77
|
+
provider: "vault",
|
|
78
|
+
displayName: "HashiCorp Vault",
|
|
79
|
+
authMethod: "credentials",
|
|
80
|
+
configFields: [{ key: "address", label: "Vault Address", required: true }],
|
|
81
|
+
credentialFields: [{ key: "token", label: "Vault Token", secret: true }],
|
|
82
|
+
},
|
|
83
|
+
// Metrics - Credentials
|
|
84
|
+
{
|
|
85
|
+
category: "metrics",
|
|
86
|
+
provider: "datadog",
|
|
87
|
+
displayName: "Datadog",
|
|
88
|
+
authMethod: "credentials",
|
|
89
|
+
configFields: [
|
|
90
|
+
{
|
|
91
|
+
key: "site",
|
|
92
|
+
label: "Datadog Site",
|
|
93
|
+
default: "datadoghq.com",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
credentialFields: [
|
|
97
|
+
{
|
|
98
|
+
key: "api_key",
|
|
99
|
+
label: "API Key",
|
|
100
|
+
secret: true,
|
|
101
|
+
required: true,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
const categories = ["notifications", "secrets", "metrics"];
|
|
107
|
+
export function getCategories() {
|
|
108
|
+
return [...categories];
|
|
109
|
+
}
|
|
110
|
+
export function getProviders(category) {
|
|
111
|
+
return providers.filter((p) => p.category === category);
|
|
112
|
+
}
|
|
113
|
+
export function getProvider(category, provider) {
|
|
114
|
+
return (providers.find((p) => p.category === category && p.provider === provider) ??
|
|
115
|
+
null);
|
|
116
|
+
}
|
|
117
|
+
export function getAllProvidersByCategory() {
|
|
118
|
+
const map = new Map();
|
|
119
|
+
for (const cat of categories) {
|
|
120
|
+
map.set(cat, getProviders(cat));
|
|
121
|
+
}
|
|
122
|
+
return map;
|
|
123
|
+
}
|
package/dist/schemas/state.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Type, type Static } from "typebox";
|
|
2
|
-
export declare const EnvironmentConfigSchema: Type.TObject<{
|
|
2
|
+
export declare const EnvironmentConfigSchema: Type.TObject<{
|
|
3
|
+
variables: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
|
|
4
|
+
}>;
|
|
3
5
|
export type EnvironmentConfig = Static<typeof EnvironmentConfigSchema>;
|
|
4
6
|
export declare const CloudConfigSchema: Type.TObject<{
|
|
5
7
|
authUrl: Type.TString;
|
|
@@ -15,14 +17,22 @@ export declare const DiscoveryConfigSchema: Type.TObject<{
|
|
|
15
17
|
}>;
|
|
16
18
|
export type DiscoveryConfig = Static<typeof DiscoveryConfigSchema>;
|
|
17
19
|
export declare const EnvironmentsConfigSchema: Type.TIntersect<[Type.TObject<{
|
|
18
|
-
default: Type.TObject<{
|
|
19
|
-
|
|
20
|
+
default: Type.TObject<{
|
|
21
|
+
variables: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
|
|
22
|
+
}>;
|
|
23
|
+
}>, Type.TRecord<"^.*$", Type.TObject<{
|
|
24
|
+
variables: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
|
|
25
|
+
}>>]>;
|
|
20
26
|
export declare const StateFileSchema: Type.TObject<{
|
|
21
27
|
stateVersion: Type.TLiteral<1>;
|
|
22
28
|
projectId: Type.TString;
|
|
23
29
|
environments: Type.TIntersect<[Type.TObject<{
|
|
24
|
-
default: Type.TObject<{
|
|
25
|
-
|
|
30
|
+
default: Type.TObject<{
|
|
31
|
+
variables: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
|
|
32
|
+
}>;
|
|
33
|
+
}>, Type.TRecord<"^.*$", Type.TObject<{
|
|
34
|
+
variables: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
|
|
35
|
+
}>>]>;
|
|
26
36
|
hub: Type.TObject<{
|
|
27
37
|
baseUrl: Type.TString;
|
|
28
38
|
clientId: Type.TOptional<Type.TString>;
|
package/dist/schemas/state.js
CHANGED
|
@@ -8,7 +8,9 @@ import { Type } from "typebox";
|
|
|
8
8
|
*/
|
|
9
9
|
const authUrl = "https://www.getgriffinapp.com/api/auth";
|
|
10
10
|
const hubBaseUrl = "https://griffin-hub.fly.dev";
|
|
11
|
-
export const EnvironmentConfigSchema = Type.Object({
|
|
11
|
+
export const EnvironmentConfigSchema = Type.Object({
|
|
12
|
+
variables: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
13
|
+
});
|
|
12
14
|
export const CloudConfigSchema = Type.Object({
|
|
13
15
|
authUrl: Type.String(),
|
|
14
16
|
});
|
package/dist/utils/terminal.d.ts
CHANGED
|
@@ -63,6 +63,10 @@ export declare class Terminal {
|
|
|
63
63
|
* Prompt user for text input
|
|
64
64
|
*/
|
|
65
65
|
input(message: string, initial?: string): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Prompt user for hidden input (password/secret)
|
|
68
|
+
*/
|
|
69
|
+
password(message: string): Promise<string>;
|
|
66
70
|
/**
|
|
67
71
|
* Create a formatted table
|
|
68
72
|
*/
|
package/dist/utils/terminal.js
CHANGED
|
@@ -105,6 +105,17 @@ export class Terminal {
|
|
|
105
105
|
});
|
|
106
106
|
return response.value;
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Prompt user for hidden input (password/secret)
|
|
110
|
+
*/
|
|
111
|
+
async password(message) {
|
|
112
|
+
const response = await enquirer.prompt({
|
|
113
|
+
type: "password",
|
|
114
|
+
name: "value",
|
|
115
|
+
message,
|
|
116
|
+
});
|
|
117
|
+
return response.value;
|
|
118
|
+
}
|
|
108
119
|
/**
|
|
109
120
|
* Create a formatted table
|
|
110
121
|
*/
|