@griffin-app/griffin-cli 1.0.30 → 1.0.32

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 CHANGED
@@ -18,7 +18,7 @@ import { executeLogin } from "./commands/hub/login.js";
18
18
  import { executeLogout } from "./commands/hub/logout.js";
19
19
  import { executeIntegrationsConnect } from "./commands/hub/integrations.js";
20
20
  import { executeNotificationsList, executeNotificationsTest, } from "./commands/hub/notifications.js";
21
- import { executeSecretsList, executeSecretsSet, executeSecretsGet, executeSecretsDelete, } from "./commands/hub/secrets.js";
21
+ import { executeSecretsList, executeSecretsSet, executeSecretsDelete, } from "./commands/hub/secrets.js";
22
22
  import { executeIntegrationsList, executeIntegrationsShow, executeIntegrationsUpdate, executeIntegrationsRemove, } from "./commands/hub/integrations.js";
23
23
  import packageInfo from "../package.json" with { type: "json" };
24
24
  const program = new Command();
@@ -128,8 +128,8 @@ program
128
128
  .option("--monitor <nameOrId>", "Specific monitor name or ID to destroy")
129
129
  .option("--auto-approve", "Skip confirmation prompt")
130
130
  .option("--dry-run", "Show what would be destroyed without making changes")
131
- .action(async (env, options) => {
132
- await executeDestroy({ ...options, env, json: program.opts().json });
131
+ .action(async (options) => {
132
+ await executeDestroy({ ...options, json: program.opts().json });
133
133
  });
134
134
  // Auth command group
135
135
  const auth = program.command("auth").description("Manage authentication");
@@ -246,18 +246,6 @@ secrets
246
246
  json: program.opts().json,
247
247
  });
248
248
  });
249
- secrets
250
- .command("get <name>")
251
- .description("Show secret metadata")
252
- .option("--env <name>", "Environment name", "default")
253
- .option("--json", "Output as JSON")
254
- .action(async (name, options) => {
255
- await executeSecretsGet({
256
- name,
257
- environment: options.env,
258
- json: program.opts().json ?? options.json,
259
- });
260
- });
261
249
  secrets
262
250
  .command("delete <name>")
263
251
  .description("Delete a secret")
@@ -28,13 +28,9 @@ export const executeSecretsList = createCommandHandler("secrets list", async (op
28
28
  }
29
29
  output.info(`Secrets (environment: ${env})`);
30
30
  output.blank();
31
- const table = output.table({
32
- head: ["Name"],
33
- });
34
- for (const s of secrets) {
35
- table.push([s.name]);
31
+ for (const { name } of secrets) {
32
+ output.log(name);
36
33
  }
37
- output.log(table.toString());
38
34
  });
39
35
  function promptSecret(promptText) {
40
36
  return new Promise((resolve) => {
@@ -5,20 +5,9 @@
5
5
  * - `env`: reads secrets from environment variables (local dev, no hub needed)
6
6
  * - `hub`: resolves secrets via POST /secrets/resolve on the hub API
7
7
  */
8
- import { type SecretProvider } from "@griffin-app/griffin-executor";
9
- import type { GriffinHubSdk } from "@griffin-app/griffin-hub-sdk";
8
+ import { HubSecretProvider, type SecretProvider } from "@griffin-app/griffin-executor";
9
+ export { HubSecretProvider };
10
10
  /**
11
11
  * Create an env-based secret provider for fully-local CLI runs (no hub).
12
12
  */
13
13
  export declare function createEnvSecretsProvider(): SecretProvider;
14
- /**
15
- * SecretProvider that resolves secrets via the hub's POST /secrets/resolve endpoint.
16
- * The hub is the sole gateway for AWS Secrets Manager — no credentials leave the hub.
17
- */
18
- export declare class HubSecretProvider implements SecretProvider {
19
- readonly name = "hub";
20
- private readonly sdk;
21
- private readonly environment;
22
- constructor(sdk: GriffinHubSdk, environment: string);
23
- resolve(ref: string): Promise<string>;
24
- }
@@ -5,7 +5,9 @@
5
5
  * - `env`: reads secrets from environment variables (local dev, no hub needed)
6
6
  * - `hub`: resolves secrets via POST /secrets/resolve on the hub API
7
7
  */
8
- import { EnvSecretProvider, } from "@griffin-app/griffin-executor";
8
+ import { EnvSecretProvider, HubSecretProvider, } from "@griffin-app/griffin-executor";
9
+ // Re-export so existing imports from this module continue to work
10
+ export { HubSecretProvider };
9
11
  /**
10
12
  * Environment variable name for selecting the local secrets provider.
11
13
  * Only "env" is supported for fully-local runs without a hub.
@@ -20,26 +22,3 @@ export function createEnvSecretsProvider() {
20
22
  env: process.env,
21
23
  });
22
24
  }
23
- /**
24
- * SecretProvider that resolves secrets via the hub's POST /secrets/resolve endpoint.
25
- * The hub is the sole gateway for AWS Secrets Manager — no credentials leave the hub.
26
- */
27
- export class HubSecretProvider {
28
- name = "hub";
29
- sdk;
30
- environment;
31
- constructor(sdk, environment) {
32
- this.sdk = sdk;
33
- this.environment = environment;
34
- }
35
- async resolve(ref) {
36
- const response = await this.sdk.postSecretsResolve({
37
- body: { name: ref, environment: this.environment },
38
- });
39
- const value = response.data?.data?.value;
40
- if (value === undefined) {
41
- throw new Error(`Secret "${ref}" not found for environment "${this.environment}"`);
42
- }
43
- return value;
44
- }
45
- }
@@ -1,5 +1,18 @@
1
1
  import { getEnvironment } from "./state.js";
2
2
  import { isNodeRef } from "@griffin-app/griffin-core/schema";
3
+ function isTemplateRef(value) {
4
+ if (typeof value !== "object" || value === null) {
5
+ return false;
6
+ }
7
+ const obj = value;
8
+ if (!("$template" in obj) ||
9
+ typeof obj.$template !== "object" ||
10
+ obj.$template === null) {
11
+ return false;
12
+ }
13
+ const tmpl = obj.$template;
14
+ return Array.isArray(tmpl.strings) && Array.isArray(tmpl.values);
15
+ }
3
16
  /**
4
17
  * Load variables from state file for a specific environment.
5
18
  *
@@ -61,6 +74,38 @@ function resolveVariable(varRef, variables) {
61
74
  const placeholder = `\${${key}}`;
62
75
  return template.replace(placeholder, value);
63
76
  }
77
+ /**
78
+ * Resolve variables in a template, using partial application.
79
+ * Resolves $variable values, merges them into adjacent strings.
80
+ * Returns a plain string if all values resolved, or a reduced $template.
81
+ */
82
+ function resolveTemplateVariables(tmpl, variables) {
83
+ const { strings, values } = tmpl.$template;
84
+ const newStrings = [strings[0]];
85
+ const newValues = [];
86
+ for (let i = 0; i < values.length; i++) {
87
+ const val = values[i];
88
+ if (isVariableRef(val)) {
89
+ const resolved = resolveVariable(val, variables);
90
+ // Merge resolved value into the last string segment
91
+ newStrings[newStrings.length - 1] += resolved + strings[i + 1];
92
+ }
93
+ else if (isStringLiteral(val)) {
94
+ // Unwrap literals and merge
95
+ newStrings[newStrings.length - 1] += val.$literal + strings[i + 1];
96
+ }
97
+ else {
98
+ // Keep this value and its trailing string
99
+ newValues.push(val);
100
+ newStrings.push(strings[i + 1]);
101
+ }
102
+ }
103
+ // If all values resolved, collapse to a $literal
104
+ if (newValues.length === 0) {
105
+ return { $literal: newStrings[0] };
106
+ }
107
+ return { $template: { strings: newStrings, values: newValues } };
108
+ }
64
109
  /**
65
110
  * Recursively walk a monitor object and resolve all variable references.
66
111
  *
@@ -74,10 +119,13 @@ function resolveVariable(varRef, variables) {
74
119
  export function resolveVariablesInMonitor(obj, variables) {
75
120
  // Check if this is a variable reference
76
121
  if (isVariableRef(obj)) {
77
- return resolveVariable(obj, variables);
122
+ return { $literal: resolveVariable(obj, variables) };
123
+ }
124
+ if (isTemplateRef(obj)) {
125
+ return resolveTemplateVariables(obj, variables);
78
126
  }
79
127
  if (isStringLiteral(obj)) {
80
- return obj.$literal;
128
+ return obj;
81
129
  }
82
130
  if (isNodeRef(obj)) {
83
131
  return obj;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,127 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { resolveVariablesInMonitor } from "./variables.js";
3
+ describe("resolveVariablesInMonitor", () => {
4
+ const variables = { env: "staging", version: "v2" };
5
+ it("should resolve a simple variable ref to $literal", () => {
6
+ const result = resolveVariablesInMonitor({ $variable: { key: "env" } }, variables);
7
+ expect(result).toEqual({ $literal: "staging" });
8
+ });
9
+ it("should resolve a variable with template to $literal", () => {
10
+ const result = resolveVariablesInMonitor({ $variable: { key: "env", template: "https://${env}.api.com" } }, variables);
11
+ expect(result).toEqual({ $literal: "https://staging.api.com" });
12
+ });
13
+ it("should pass through $literal unchanged", () => {
14
+ const literal = { $literal: "hello" };
15
+ const result = resolveVariablesInMonitor(literal, variables);
16
+ expect(result).toEqual(literal);
17
+ });
18
+ it("should pass through $nodeRef unchanged", () => {
19
+ const nodeRef = {
20
+ $nodeRef: { nodeId: "step1", subject: "body", path: ["id"] },
21
+ };
22
+ const result = resolveVariablesInMonitor(nodeRef, variables);
23
+ expect(result).toEqual(nodeRef);
24
+ });
25
+ describe("$template resolution", () => {
26
+ it("should fully collapse a template with only variables to $literal", () => {
27
+ const tmpl = {
28
+ $template: {
29
+ strings: ["https://", ".api.com/", "/health"],
30
+ values: [
31
+ { $variable: { key: "env" } },
32
+ { $variable: { key: "version" } },
33
+ ],
34
+ },
35
+ };
36
+ const result = resolveVariablesInMonitor(tmpl, variables);
37
+ expect(result).toEqual({ $literal: "https://staging.api.com/v2/health" });
38
+ });
39
+ it("should partially resolve a template with mixed variable and secret", () => {
40
+ const tmpl = {
41
+ $template: {
42
+ strings: ["https://", ".api.com?key=", ""],
43
+ values: [
44
+ { $variable: { key: "env" } },
45
+ { $secret: { ref: "API_KEY" } },
46
+ ],
47
+ },
48
+ };
49
+ const result = resolveVariablesInMonitor(tmpl, variables);
50
+ expect(result).toEqual({
51
+ $template: {
52
+ strings: ["https://staging.api.com?key=", ""],
53
+ values: [{ $secret: { ref: "API_KEY" } }],
54
+ },
55
+ });
56
+ });
57
+ it("should partially resolve a template with mixed variable and nodeRef", () => {
58
+ const tmpl = {
59
+ $template: {
60
+ strings: ["/api/", "/resource/", ""],
61
+ values: [
62
+ { $variable: { key: "version" } },
63
+ { $nodeRef: { nodeId: "step1", subject: "body", path: ["id"] } },
64
+ ],
65
+ },
66
+ };
67
+ const result = resolveVariablesInMonitor(tmpl, variables);
68
+ expect(result).toEqual({
69
+ $template: {
70
+ strings: ["/api/v2/resource/", ""],
71
+ values: [
72
+ { $nodeRef: { nodeId: "step1", subject: "body", path: ["id"] } },
73
+ ],
74
+ },
75
+ });
76
+ });
77
+ it("should pass through a template with no variables unchanged", () => {
78
+ const tmpl = {
79
+ $template: {
80
+ strings: ["Bearer ", ""],
81
+ values: [{ $secret: { ref: "TOKEN" } }],
82
+ },
83
+ };
84
+ const result = resolveVariablesInMonitor(tmpl, variables);
85
+ expect(result).toEqual(tmpl);
86
+ });
87
+ it("should handle adjacent variable interpolations", () => {
88
+ const tmpl = {
89
+ $template: {
90
+ strings: ["", "", "/path"],
91
+ values: [
92
+ { $variable: { key: "env" } },
93
+ { $variable: { key: "version" } },
94
+ ],
95
+ },
96
+ };
97
+ const result = resolveVariablesInMonitor(tmpl, variables);
98
+ expect(result).toEqual({ $literal: "stagingv2/path" });
99
+ });
100
+ it("should resolve templates nested in monitor structure", () => {
101
+ const monitor = {
102
+ nodes: [
103
+ {
104
+ type: "HTTP_REQUEST",
105
+ path: {
106
+ $template: {
107
+ strings: ["/api/", "/health"],
108
+ values: [{ $variable: { key: "version" } }],
109
+ },
110
+ },
111
+ base: {
112
+ $template: {
113
+ strings: ["https://", ".api.com"],
114
+ values: [{ $variable: { key: "env" } }],
115
+ },
116
+ },
117
+ },
118
+ ],
119
+ };
120
+ const result = resolveVariablesInMonitor(monitor, variables);
121
+ expect(result.nodes[0].path).toEqual({ $literal: "/api/v2/health" });
122
+ expect(result.nodes[0].base).toEqual({
123
+ $literal: "https://staging.api.com",
124
+ });
125
+ });
126
+ });
127
+ });
@@ -1,6 +1,6 @@
1
1
  import "tsx";
2
2
  import { Value } from "typebox/value";
3
- import { executeMonitorV1, AxiosAdapter, planHasSecrets, } from "@griffin-app/griffin-executor";
3
+ import { executeMonitorV1, AxiosAdapter, } from "@griffin-app/griffin-executor";
4
4
  import { MonitorDSLSchema } from "@griffin-app/griffin-core/schema";
5
5
  import { randomUUID } from "crypto";
6
6
  import { loadVariables } from "./core/variables.js";
@@ -28,19 +28,13 @@ export async function runTestFile(filePath, envName) {
28
28
  const resolvedMonitor = resolveMonitor(rawMonitor, projectId, envName, variables);
29
29
  // Create secret provider: prefer hub (authenticated) for cloud secrets,
30
30
  // fall back to env provider for fully-local runs.
31
- let secretProvider;
32
31
  const monitorV1 = { ...resolvedMonitor, id: randomUUID() };
33
- if (planHasSecrets(monitorV1)) {
34
- try {
35
- const sdk = await createSdkFromState();
36
- secretProvider = new HubSecretProvider(sdk, envName);
37
- }
38
- catch {
39
- // Hub not available — fall back to env provider
40
- secretProvider = createEnvSecretsProvider();
41
- }
32
+ let secretProvider;
33
+ try {
34
+ const sdk = await createSdkFromState();
35
+ secretProvider = new HubSecretProvider(sdk, envName);
42
36
  }
43
- else {
37
+ catch {
44
38
  secretProvider = createEnvSecretsProvider();
45
39
  }
46
40
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@griffin-app/griffin-cli",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "CLI tool for running and managing griffin API tests",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,9 +24,9 @@
24
24
  "author": "",
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
- "@griffin-app/griffin-hub-sdk": "1.0.27",
28
- "@griffin-app/griffin-executor": "0.1.2",
29
- "@griffin-app/griffin-core": "0.2.2",
27
+ "@griffin-app/griffin-core": "0.2.4",
28
+ "@griffin-app/griffin-executor": "0.1.7",
29
+ "@griffin-app/griffin-hub-sdk": "1.0.29",
30
30
  "better-auth": "^1.4.17",
31
31
  "cli-table3": "^0.6.5",
32
32
  "commander": "^12.1.0",