@griffin-app/griffin-ts 0.1.14 → 0.1.15
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/builder.test.js +8 -21
- package/dist/builder.test.js.map +1 -1
- package/dist/schema.d.ts +0 -20
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +0 -1
- package/dist/schema.js.map +1 -1
- package/dist/secrets.d.ts +4 -9
- package/dist/secrets.d.ts.map +1 -1
- package/dist/secrets.js +12 -23
- package/dist/secrets.js.map +1 -1
- package/dist/secrets.test.js +56 -119
- package/dist/secrets.test.js.map +1 -1
- package/dist/sequential-builder.test.js +4 -10
- package/dist/sequential-builder.test.js.map +1 -1
- package/package.json +1 -1
- package/src/builder.test.ts +8 -21
- package/src/schema.ts +0 -1
- package/src/secrets.test.ts +62 -141
- package/src/secrets.ts +13 -33
- package/src/sequential-builder.test.ts +4 -10
package/src/secrets.test.ts
CHANGED
|
@@ -3,46 +3,32 @@ import { secret, isSecretRef } from "./secrets.js";
|
|
|
3
3
|
|
|
4
4
|
describe("Secret References", () => {
|
|
5
5
|
describe("Basic Secret Creation", () => {
|
|
6
|
-
it("should create
|
|
7
|
-
const ref = secret("
|
|
6
|
+
it("should create secret with ref only", () => {
|
|
7
|
+
const ref = secret("API_KEY");
|
|
8
8
|
|
|
9
9
|
expect(ref).toEqual({
|
|
10
10
|
$secret: {
|
|
11
|
-
provider: "env",
|
|
12
11
|
ref: "API_KEY",
|
|
13
12
|
},
|
|
14
13
|
});
|
|
15
14
|
});
|
|
16
15
|
|
|
17
|
-
it("should create
|
|
18
|
-
const ref = secret("
|
|
16
|
+
it("should create secret with alphanumeric and underscore", () => {
|
|
17
|
+
const ref = secret("my_secret_123");
|
|
19
18
|
|
|
20
19
|
expect(ref).toEqual({
|
|
21
20
|
$secret: {
|
|
22
|
-
|
|
23
|
-
ref: "prod/api-key",
|
|
21
|
+
ref: "my_secret_123",
|
|
24
22
|
},
|
|
25
23
|
});
|
|
26
24
|
});
|
|
27
25
|
|
|
28
|
-
it("should
|
|
29
|
-
const ref = secret("
|
|
26
|
+
it("should trim ref", () => {
|
|
27
|
+
const ref = secret(" API_KEY ");
|
|
30
28
|
|
|
31
29
|
expect(ref).toEqual({
|
|
32
30
|
$secret: {
|
|
33
|
-
|
|
34
|
-
ref: "secret/data/api",
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("should create Doppler secret", () => {
|
|
40
|
-
const ref = secret("doppler:backend/prod/API_KEY");
|
|
41
|
-
|
|
42
|
-
expect(ref).toEqual({
|
|
43
|
-
$secret: {
|
|
44
|
-
provider: "doppler",
|
|
45
|
-
ref: "backend/prod/API_KEY",
|
|
31
|
+
ref: "API_KEY",
|
|
46
32
|
},
|
|
47
33
|
});
|
|
48
34
|
});
|
|
@@ -50,39 +36,36 @@ describe("Secret References", () => {
|
|
|
50
36
|
|
|
51
37
|
describe("Secret Options", () => {
|
|
52
38
|
it("should include version when specified", () => {
|
|
53
|
-
const ref = secret("
|
|
39
|
+
const ref = secret("prod_api_key", { version: "v1.2.3" });
|
|
54
40
|
|
|
55
41
|
expect(ref).toEqual({
|
|
56
42
|
$secret: {
|
|
57
|
-
|
|
58
|
-
ref: "prod/api-key",
|
|
43
|
+
ref: "prod_api_key",
|
|
59
44
|
version: "v1.2.3",
|
|
60
45
|
},
|
|
61
46
|
});
|
|
62
47
|
});
|
|
63
48
|
|
|
64
49
|
it("should include field when specified", () => {
|
|
65
|
-
const ref = secret("
|
|
50
|
+
const ref = secret("db_credentials", { field: "password" });
|
|
66
51
|
|
|
67
52
|
expect(ref).toEqual({
|
|
68
53
|
$secret: {
|
|
69
|
-
|
|
70
|
-
ref: "prod/db-credentials",
|
|
54
|
+
ref: "db_credentials",
|
|
71
55
|
field: "password",
|
|
72
56
|
},
|
|
73
57
|
});
|
|
74
58
|
});
|
|
75
59
|
|
|
76
60
|
it("should include both version and field", () => {
|
|
77
|
-
const ref = secret("
|
|
61
|
+
const ref = secret("db_credentials", {
|
|
78
62
|
version: "latest",
|
|
79
63
|
field: "username",
|
|
80
64
|
});
|
|
81
65
|
|
|
82
66
|
expect(ref).toEqual({
|
|
83
67
|
$secret: {
|
|
84
|
-
|
|
85
|
-
ref: "prod/db-credentials",
|
|
68
|
+
ref: "db_credentials",
|
|
86
69
|
version: "latest",
|
|
87
70
|
field: "username",
|
|
88
71
|
},
|
|
@@ -90,83 +73,65 @@ describe("Secret References", () => {
|
|
|
90
73
|
});
|
|
91
74
|
});
|
|
92
75
|
|
|
93
|
-
describe("
|
|
94
|
-
it("should
|
|
95
|
-
expect(secret("
|
|
96
|
-
$secret: {
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
expect(secret("gcp:my-secret")).toEqual({
|
|
100
|
-
$secret: { provider: "gcp", ref: "my-secret" },
|
|
76
|
+
describe("Valid Names", () => {
|
|
77
|
+
it("should accept names starting with letter", () => {
|
|
78
|
+
expect(secret("API_KEY")).toEqual({
|
|
79
|
+
$secret: { ref: "API_KEY" },
|
|
101
80
|
});
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
$secret: { provider: "azure", ref: "my-secret" },
|
|
81
|
+
expect(secret("DatabasePassword")).toEqual({
|
|
82
|
+
$secret: { ref: "DatabasePassword" },
|
|
105
83
|
});
|
|
106
84
|
});
|
|
107
85
|
|
|
108
|
-
it("should
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
expect(ref).toEqual({
|
|
112
|
-
$secret: {
|
|
113
|
-
provider: "aws",
|
|
114
|
-
ref: "path/to/nested/secret",
|
|
115
|
-
},
|
|
86
|
+
it("should accept names starting with underscore", () => {
|
|
87
|
+
expect(secret("_private_key")).toEqual({
|
|
88
|
+
$secret: { ref: "_private_key" },
|
|
116
89
|
});
|
|
117
90
|
});
|
|
118
91
|
|
|
119
|
-
it("should
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
expect(ref).toEqual({
|
|
123
|
-
$secret: {
|
|
124
|
-
provider: "env",
|
|
125
|
-
ref: "API_KEY_123",
|
|
126
|
-
},
|
|
92
|
+
it("should accept alphanumeric and underscores", () => {
|
|
93
|
+
expect(secret("API_KEY_123")).toEqual({
|
|
94
|
+
$secret: { ref: "API_KEY_123" },
|
|
127
95
|
});
|
|
128
96
|
});
|
|
97
|
+
});
|
|
129
98
|
|
|
130
|
-
|
|
131
|
-
|
|
99
|
+
describe("Error Handling", () => {
|
|
100
|
+
it("should throw error when ref is empty", () => {
|
|
101
|
+
expect(() => secret("")).toThrow("Secret ref cannot be empty");
|
|
102
|
+
});
|
|
132
103
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
provider: "aws",
|
|
136
|
-
ref: "prod/my-api_key-v2",
|
|
137
|
-
},
|
|
138
|
-
});
|
|
104
|
+
it("should throw error when ref is only whitespace", () => {
|
|
105
|
+
expect(() => secret(" ")).toThrow("Secret ref cannot be empty");
|
|
139
106
|
});
|
|
140
|
-
});
|
|
141
107
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
'Secret path must include provider: "provider:path"',
|
|
108
|
+
it("should throw error when ref starts with number", () => {
|
|
109
|
+
expect(() => secret("123_KEY")).toThrow(
|
|
110
|
+
"Secret ref must start with a letter or underscore",
|
|
146
111
|
);
|
|
147
112
|
});
|
|
148
113
|
|
|
149
|
-
it("should throw error when
|
|
150
|
-
expect(() => secret("
|
|
151
|
-
"Secret
|
|
114
|
+
it("should throw error when ref contains hyphen", () => {
|
|
115
|
+
expect(() => secret("api-key")).toThrow(
|
|
116
|
+
"Secret ref must start with a letter or underscore",
|
|
152
117
|
);
|
|
153
118
|
});
|
|
154
119
|
|
|
155
|
-
it("should throw error when ref
|
|
156
|
-
expect(() => secret("
|
|
157
|
-
"Secret
|
|
120
|
+
it("should throw error when ref contains special characters", () => {
|
|
121
|
+
expect(() => secret("api.key")).toThrow(
|
|
122
|
+
"Secret ref must start with a letter or underscore",
|
|
158
123
|
);
|
|
159
124
|
});
|
|
160
125
|
});
|
|
161
126
|
|
|
162
127
|
describe("Type Guard", () => {
|
|
163
128
|
it("should identify valid secret refs", () => {
|
|
164
|
-
const ref = secret("
|
|
129
|
+
const ref = secret("API_KEY");
|
|
165
130
|
expect(isSecretRef(ref)).toBe(true);
|
|
166
131
|
});
|
|
167
132
|
|
|
168
133
|
it("should identify secret refs with options", () => {
|
|
169
|
-
const ref = secret("
|
|
134
|
+
const ref = secret("key", { version: "v1", field: "password" });
|
|
170
135
|
expect(isSecretRef(ref)).toBe(true);
|
|
171
136
|
});
|
|
172
137
|
|
|
@@ -187,27 +152,16 @@ describe("Secret References", () => {
|
|
|
187
152
|
expect(isSecretRef({ $secret: null })).toBe(false);
|
|
188
153
|
expect(isSecretRef({ $secret: "string" })).toBe(false);
|
|
189
154
|
expect(isSecretRef({ $secret: {} })).toBe(false);
|
|
190
|
-
expect(isSecretRef({ $secret: {
|
|
191
|
-
expect(isSecretRef({ $secret: { ref: "key" } })).toBe(false);
|
|
155
|
+
expect(isSecretRef({ $secret: { ref: "key" } })).toBe(true); // valid - only ref required
|
|
192
156
|
});
|
|
193
157
|
|
|
194
158
|
it("should accept valid $secret structure", () => {
|
|
159
|
+
expect(isSecretRef({ $secret: { ref: "key" } })).toBe(true);
|
|
160
|
+
expect(isSecretRef({ $secret: { ref: "VAR", version: "v1" } })).toBe(
|
|
161
|
+
true,
|
|
162
|
+
);
|
|
195
163
|
expect(
|
|
196
|
-
isSecretRef({
|
|
197
|
-
$secret: { provider: "aws", ref: "key" },
|
|
198
|
-
}),
|
|
199
|
-
).toBe(true);
|
|
200
|
-
|
|
201
|
-
expect(
|
|
202
|
-
isSecretRef({
|
|
203
|
-
$secret: { provider: "env", ref: "VAR", version: "v1" },
|
|
204
|
-
}),
|
|
205
|
-
).toBe(true);
|
|
206
|
-
|
|
207
|
-
expect(
|
|
208
|
-
isSecretRef({
|
|
209
|
-
$secret: { provider: "vault", ref: "path", field: "password" },
|
|
210
|
-
}),
|
|
164
|
+
isSecretRef({ $secret: { ref: "path", field: "password" } }),
|
|
211
165
|
).toBe(true);
|
|
212
166
|
});
|
|
213
167
|
});
|
|
@@ -215,31 +169,30 @@ describe("Secret References", () => {
|
|
|
215
169
|
describe("Integration Examples", () => {
|
|
216
170
|
it("should work in header configurations", () => {
|
|
217
171
|
const headers = {
|
|
218
|
-
Authorization: secret("
|
|
219
|
-
"X-API-Key": secret("
|
|
172
|
+
Authorization: secret("API_TOKEN"),
|
|
173
|
+
"X-API-Key": secret("API_KEY"),
|
|
220
174
|
};
|
|
221
175
|
|
|
222
176
|
expect(headers["Authorization"]).toEqual({
|
|
223
|
-
$secret: {
|
|
177
|
+
$secret: { ref: "API_TOKEN" },
|
|
224
178
|
});
|
|
225
179
|
expect(headers["X-API-Key"]).toEqual({
|
|
226
|
-
$secret: {
|
|
180
|
+
$secret: { ref: "API_KEY" },
|
|
227
181
|
});
|
|
228
182
|
});
|
|
229
183
|
|
|
230
184
|
it("should work in body configurations", () => {
|
|
231
185
|
const body = {
|
|
232
|
-
apiKey: secret("
|
|
233
|
-
dbPassword: secret("
|
|
186
|
+
apiKey: secret("API_KEY"),
|
|
187
|
+
dbPassword: secret("db_credentials", { field: "password" }),
|
|
234
188
|
};
|
|
235
189
|
|
|
236
190
|
expect(body.apiKey).toEqual({
|
|
237
|
-
$secret: {
|
|
191
|
+
$secret: { ref: "API_KEY" },
|
|
238
192
|
});
|
|
239
193
|
expect(body.dbPassword).toEqual({
|
|
240
194
|
$secret: {
|
|
241
|
-
|
|
242
|
-
ref: "prod/db-password",
|
|
195
|
+
ref: "db_credentials",
|
|
243
196
|
field: "password",
|
|
244
197
|
},
|
|
245
198
|
});
|
|
@@ -250,49 +203,17 @@ describe("Secret References", () => {
|
|
|
250
203
|
database: {
|
|
251
204
|
host: "localhost",
|
|
252
205
|
credentials: {
|
|
253
|
-
username: secret("
|
|
254
|
-
password: secret("
|
|
206
|
+
username: secret("db_username"),
|
|
207
|
+
password: secret("db_password"),
|
|
255
208
|
},
|
|
256
209
|
},
|
|
257
210
|
};
|
|
258
211
|
|
|
259
212
|
expect(config.database.credentials.username).toEqual({
|
|
260
|
-
$secret: {
|
|
213
|
+
$secret: { ref: "db_username" },
|
|
261
214
|
});
|
|
262
215
|
expect(config.database.credentials.password).toEqual({
|
|
263
|
-
$secret: {
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
describe("Real-world Provider Examples", () => {
|
|
269
|
-
it("should handle AWS Secrets Manager paths", () => {
|
|
270
|
-
expect(secret("aws:production/api/key")).toEqual({
|
|
271
|
-
$secret: { provider: "aws", ref: "production/api/key" },
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
expect(secret("aws:prod/rds/credentials")).toEqual({
|
|
275
|
-
$secret: { provider: "aws", ref: "prod/rds/credentials" },
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it("should handle HashiCorp Vault paths", () => {
|
|
280
|
-
expect(secret("vault:secret/data/api")).toEqual({
|
|
281
|
-
$secret: { provider: "vault", ref: "secret/data/api" },
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
expect(secret("vault:kv/prod/database")).toEqual({
|
|
285
|
-
$secret: { provider: "vault", ref: "kv/prod/database" },
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it("should handle environment variables", () => {
|
|
290
|
-
expect(secret("env:DATABASE_URL")).toEqual({
|
|
291
|
-
$secret: { provider: "env", ref: "DATABASE_URL" },
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
expect(secret("env:API_KEY")).toEqual({
|
|
295
|
-
$secret: { provider: "env", ref: "API_KEY" },
|
|
216
|
+
$secret: { ref: "db_password" },
|
|
296
217
|
});
|
|
297
218
|
});
|
|
298
219
|
});
|
package/src/secrets.ts
CHANGED
|
@@ -7,15 +7,12 @@ export interface SecretOptions {
|
|
|
7
7
|
field?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const SECRET_REF_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Create a secret reference for use in endpoint headers or body.
|
|
12
14
|
*
|
|
13
|
-
* @param
|
|
14
|
-
* Examples:
|
|
15
|
-
* - "env:API_KEY" - Environment variable
|
|
16
|
-
* - "aws:prod/api-key" - AWS Secrets Manager
|
|
17
|
-
* - "vault:secret/data/api" - HashiCorp Vault
|
|
18
|
-
* - "doppler:backend/prod/API_KEY" - Doppler
|
|
15
|
+
* @param ref - Secret name (alphanumeric and underscore only)
|
|
19
16
|
*
|
|
20
17
|
* @param options - Optional version pinning or field extraction
|
|
21
18
|
*
|
|
@@ -25,40 +22,26 @@ export interface SecretOptions {
|
|
|
25
22
|
* method: GET,
|
|
26
23
|
* path: "/api/data",
|
|
27
24
|
* headers: {
|
|
28
|
-
* "Authorization": secret("
|
|
29
|
-
* "X-API-Key": secret("
|
|
25
|
+
* "Authorization": secret("API_KEY"),
|
|
26
|
+
* "X-API-Key": secret("LOCAL_API_KEY"),
|
|
30
27
|
* },
|
|
31
28
|
* })
|
|
32
29
|
* ```
|
|
33
30
|
*/
|
|
34
|
-
export function secret(
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
throw new Error(
|
|
39
|
-
`Secret path must include provider: "provider:path" (e.g., "aws:my-secret", "env:API_KEY"). Got: "${path}"`,
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (colonIndex === 0) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Secret path must have a provider name before the colon. Got: "${path}"`,
|
|
46
|
-
);
|
|
31
|
+
export function secret(ref: string, options?: SecretOptions): SecretRef {
|
|
32
|
+
const trimmed = ref.trim();
|
|
33
|
+
if (!trimmed.length) {
|
|
34
|
+
throw new Error("Secret ref cannot be empty");
|
|
47
35
|
}
|
|
48
|
-
|
|
49
|
-
if (colonIndex === path.length - 1) {
|
|
36
|
+
if (!SECRET_REF_REGEX.test(trimmed)) {
|
|
50
37
|
throw new Error(
|
|
51
|
-
`Secret
|
|
38
|
+
`Secret ref must start with a letter or underscore and contain only letters, numbers, and underscores. Got: "${ref}"`,
|
|
52
39
|
);
|
|
53
40
|
}
|
|
54
41
|
|
|
55
|
-
const provider = path.slice(0, colonIndex);
|
|
56
|
-
const ref = path.slice(colonIndex + 1);
|
|
57
|
-
|
|
58
42
|
return {
|
|
59
43
|
$secret: {
|
|
60
|
-
|
|
61
|
-
ref,
|
|
44
|
+
ref: trimmed,
|
|
62
45
|
...(options?.version !== undefined && { version: options.version }),
|
|
63
46
|
...(options?.field !== undefined && { field: options.field }),
|
|
64
47
|
},
|
|
@@ -83,10 +66,7 @@ export function isSecretRef(value: unknown): value is SecretRef {
|
|
|
83
66
|
}
|
|
84
67
|
|
|
85
68
|
const secretData = obj.$secret as Record<string, unknown>;
|
|
86
|
-
return
|
|
87
|
-
typeof secretData.provider === "string" &&
|
|
88
|
-
typeof secretData.ref === "string"
|
|
89
|
-
);
|
|
69
|
+
return typeof secretData.ref === "string";
|
|
90
70
|
}
|
|
91
71
|
|
|
92
72
|
/**
|
|
@@ -399,17 +399,14 @@ describe("Sequential Test Builder", () => {
|
|
|
399
399
|
base: "http://localhost:3000",
|
|
400
400
|
response_format: "JSON",
|
|
401
401
|
headers: {
|
|
402
|
-
Authorization: secret("
|
|
402
|
+
Authorization: secret("API_TOKEN"),
|
|
403
403
|
},
|
|
404
404
|
})
|
|
405
405
|
.build();
|
|
406
406
|
|
|
407
407
|
const endpoint = monitor.nodes[0] as any;
|
|
408
408
|
expect(endpoint.headers.Authorization).toEqual({
|
|
409
|
-
$secret: {
|
|
410
|
-
provider: "env",
|
|
411
|
-
ref: "API_TOKEN",
|
|
412
|
-
},
|
|
409
|
+
$secret: { ref: "API_TOKEN" },
|
|
413
410
|
});
|
|
414
411
|
});
|
|
415
412
|
|
|
@@ -424,17 +421,14 @@ describe("Sequential Test Builder", () => {
|
|
|
424
421
|
base: "http://localhost:3000",
|
|
425
422
|
response_format: "JSON",
|
|
426
423
|
body: {
|
|
427
|
-
apiKey: secret("
|
|
424
|
+
apiKey: secret("API_KEY"),
|
|
428
425
|
},
|
|
429
426
|
})
|
|
430
427
|
.build();
|
|
431
428
|
|
|
432
429
|
const endpoint = monitor.nodes[0] as any;
|
|
433
430
|
expect(endpoint.body.apiKey).toEqual({
|
|
434
|
-
$secret: {
|
|
435
|
-
provider: "aws",
|
|
436
|
-
ref: "prod/api-key",
|
|
437
|
-
},
|
|
431
|
+
$secret: { ref: "API_KEY" },
|
|
438
432
|
});
|
|
439
433
|
});
|
|
440
434
|
|