@griffin-app/griffin-cli 1.0.31 → 1.0.33

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.
@@ -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.31",
3
+ "version": "1.0.33",
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.4",
29
- "@griffin-app/griffin-core": "0.2.2",
27
+ "@griffin-app/griffin-core": "0.2.5",
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",