hanzio 1.1.0 → 1.2.0
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 +70 -4
- package/dist/api-wrapper/builders.d.ts +13 -2
- package/dist/api-wrapper/errors.d.ts +1 -0
- package/dist/api-wrapper/headers.d.ts +2 -1
- package/dist/api-wrapper/transport.d.ts +8 -5
- package/dist/api-wrapper/types.d.ts +1 -0
- package/dist/cache/index.d.ts +26 -0
- package/dist/secrets/core.d.ts +47 -0
- package/dist/secrets/deadline.d.ts +6 -0
- package/dist/secrets/environment.d.ts +9 -0
- package/dist/secrets/errors.d.ts +11 -0
- package/dist/secrets/index.d.ts +13 -25
- package/dist/secrets/infisical-cli.d.ts +6 -0
- package/dist/secrets/infisical-config.d.ts +17 -0
- package/dist/secrets/infisical-session.d.ts +4 -0
- package/dist/secrets/infisical.d.ts +8 -13
- package/dist/secrets/loaders.d.ts +5 -12
- package/dist/secrets/vite.d.ts +7 -3
- package/dist/secrets/worker-loader.d.ts +9 -0
- package/dist/secrets/worker.d.ts +3 -0
- package/dist/src/api-wrapper/index.js +152 -122
- package/dist/src/index.js +3173 -2713
- package/dist/src/jwt/index.js +2 -2
- package/dist/src/p-queue/index.js +3 -3
- package/dist/src/secrets/index.js +670 -402
- package/dist/src/secrets/infisical.js +451 -168
- package/dist/src/secrets/loaders.js +467 -174
- package/dist/src/secrets/vite.js +33 -11
- package/dist/src/secrets/worker.js +275 -0
- package/dist/src/sitemap/index.js +1 -1
- package/dist/src/zod/index.js +7 -7
- package/dist/typedswitch/index.d.ts +9 -5
- package/package.json +12 -3
- package/src/api-wrapper/builders.ts +68 -43
- package/src/api-wrapper/create-client.ts +82 -62
- package/src/api-wrapper/errors.ts +10 -0
- package/src/api-wrapper/headers.ts +14 -1
- package/src/api-wrapper/redaction.ts +1 -0
- package/src/api-wrapper/transport.ts +34 -38
- package/src/api-wrapper/types.ts +1 -0
- package/src/cache/index.ts +228 -61
- package/src/secrets/core.ts +348 -0
- package/src/secrets/deadline.ts +69 -0
- package/src/secrets/environment.ts +69 -0
- package/src/secrets/errors.ts +29 -0
- package/src/secrets/index.ts +79 -210
- package/src/secrets/infisical-cli.ts +99 -0
- package/src/secrets/infisical-config.ts +70 -0
- package/src/secrets/infisical-session.ts +42 -0
- package/src/secrets/infisical.ts +214 -66
- package/src/secrets/loaders.ts +24 -48
- package/src/secrets/vite.ts +44 -15
- package/src/secrets/worker-loader.ts +56 -0
- package/src/secrets/worker.ts +4 -0
- package/src/typedswitch/index.ts +80 -69
|
@@ -1,3 +1,247 @@
|
|
|
1
|
+
// src/secrets/errors.ts
|
|
2
|
+
class SecretLoadError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
operation;
|
|
5
|
+
status;
|
|
6
|
+
constructor(code, message, details = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SecretLoadError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.operation = details.operation;
|
|
11
|
+
this.status = details.status;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/secrets/deadline.ts
|
|
16
|
+
async function withSecretDeadline(options, operation) {
|
|
17
|
+
const timeoutMs = options.timeoutMs ?? 30000;
|
|
18
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) {
|
|
19
|
+
throw new SecretLoadError("CONFIGURATION", "Secret timeoutMs must be a positive finite timer duration.");
|
|
20
|
+
}
|
|
21
|
+
const controller = new AbortController;
|
|
22
|
+
const cancel = () => controller.abort(new SecretLoadError("ABORTED", "Secret loading was cancelled."));
|
|
23
|
+
if (options.signal?.aborted) {
|
|
24
|
+
cancel();
|
|
25
|
+
} else {
|
|
26
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
27
|
+
}
|
|
28
|
+
const timer = setTimeout(() => controller.abort(new SecretLoadError("TIMEOUT", `Secret loading timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
29
|
+
let rejectOnAbort = () => {};
|
|
30
|
+
try {
|
|
31
|
+
const aborted = new Promise((_, reject) => {
|
|
32
|
+
rejectOnAbort = () => reject(controller.signal.reason);
|
|
33
|
+
controller.signal.addEventListener("abort", rejectOnAbort, { once: true });
|
|
34
|
+
});
|
|
35
|
+
if (controller.signal.aborted) {
|
|
36
|
+
throw controller.signal.reason;
|
|
37
|
+
}
|
|
38
|
+
return await Promise.race([operation(controller.signal), aborted]);
|
|
39
|
+
} finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
options.signal?.removeEventListener("abort", cancel);
|
|
42
|
+
controller.signal.removeEventListener("abort", rejectOnAbort);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/secrets/environment.ts
|
|
47
|
+
function getProcessEnvironment() {
|
|
48
|
+
return typeof process === "undefined" ? undefined : process.env;
|
|
49
|
+
}
|
|
50
|
+
function resolveSecretEnvironment(option, env) {
|
|
51
|
+
const explicit = typeof option === "function" ? option() : option;
|
|
52
|
+
if (typeof explicit === "object" && explicit !== null) {
|
|
53
|
+
if (typeof explicit.slug !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(explicit.slug)) {
|
|
54
|
+
throw new SecretLoadError("CONFIGURATION", "Custom secret environment requires a valid slug.");
|
|
55
|
+
}
|
|
56
|
+
return explicit.slug;
|
|
57
|
+
}
|
|
58
|
+
const selected = explicit ?? env?.SECRETS_ENV;
|
|
59
|
+
if (selected !== undefined) {
|
|
60
|
+
if (selected === "production" || selected === "prod") {
|
|
61
|
+
return "prod";
|
|
62
|
+
}
|
|
63
|
+
if (selected === "development" || selected === "dev") {
|
|
64
|
+
return "dev";
|
|
65
|
+
}
|
|
66
|
+
if (selected === "staging") {
|
|
67
|
+
return "staging";
|
|
68
|
+
}
|
|
69
|
+
throw new SecretLoadError("CONFIGURATION", "Invalid secret environment. Use dev, staging, prod, or an explicit { slug } option.");
|
|
70
|
+
}
|
|
71
|
+
if (env?.NODE_ENV === "production" || env?.NODE_ENV === "prod") {
|
|
72
|
+
return "prod";
|
|
73
|
+
}
|
|
74
|
+
if (env?.NODE_ENV === "staging") {
|
|
75
|
+
return "staging";
|
|
76
|
+
}
|
|
77
|
+
return "dev";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/secrets/core.ts
|
|
81
|
+
var registryKey = Symbol.for("hanzio.secrets.environmentWrites.v1");
|
|
82
|
+
var shared = globalThis;
|
|
83
|
+
var environmentWrites = shared[registryKey] ?? new WeakMap;
|
|
84
|
+
shared[registryKey] = environmentWrites;
|
|
85
|
+
var defaultLogger = {
|
|
86
|
+
info: (message, attrs) => console.log(`${message} environment=${attrs.environment} count=${attrs.count} source=${attrs.source} durationMs=${attrs.durationMs}`, { sources: attrs.sources }),
|
|
87
|
+
error: (message, attrs) => console.error(message, attrs)
|
|
88
|
+
};
|
|
89
|
+
function readOverrides(keys, env) {
|
|
90
|
+
if (!env) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
const writes = environmentWrites.get(env);
|
|
94
|
+
return Object.fromEntries(keys.flatMap((key) => {
|
|
95
|
+
const value = Object.hasOwn(env, key) ? env[key] : undefined;
|
|
96
|
+
if (writes?.has(key)) {
|
|
97
|
+
if (writes.get(key) === value) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
writes.delete(key);
|
|
101
|
+
}
|
|
102
|
+
return value ? [[key, value]] : [];
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
async function defineSecretSet(keys, options, environmentVariables) {
|
|
106
|
+
const keySet = [...new Set(keys)];
|
|
107
|
+
const environment = resolveSecretEnvironment(options.environment, environmentVariables);
|
|
108
|
+
const logger = options.logger === false ? undefined : options.logger ?? defaultLogger;
|
|
109
|
+
let cached;
|
|
110
|
+
let parsed;
|
|
111
|
+
let sources;
|
|
112
|
+
let inFlight;
|
|
113
|
+
const log = (level, message, attributes) => {
|
|
114
|
+
try {
|
|
115
|
+
logger?.[level](message, attributes);
|
|
116
|
+
} catch {}
|
|
117
|
+
};
|
|
118
|
+
const loadSnapshot = async (request, signal) => {
|
|
119
|
+
const local = readOverrides(keySet, environmentVariables);
|
|
120
|
+
const missing = keySet.filter((key) => !Object.hasOwn(local, key));
|
|
121
|
+
let loaderSource = options.loader.source ?? "custom loader";
|
|
122
|
+
let loaded = {};
|
|
123
|
+
if (missing.length) {
|
|
124
|
+
try {
|
|
125
|
+
loaded = await options.loader({
|
|
126
|
+
keys: missing,
|
|
127
|
+
environment,
|
|
128
|
+
signal,
|
|
129
|
+
timeoutMs: request.timeoutMs ?? options.timeoutMs ?? 30000,
|
|
130
|
+
reportSource: (source) => {
|
|
131
|
+
loaderSource = source;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error instanceof SecretLoadError) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
throw new SecretLoadError("LOADER", "Secret loader failed.");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const overrides = readOverrides(keySet, environmentVariables);
|
|
142
|
+
const next = Object.fromEntries(keySet.map((key) => {
|
|
143
|
+
if (Object.hasOwn(overrides, key)) {
|
|
144
|
+
return [key, overrides[key]];
|
|
145
|
+
}
|
|
146
|
+
if (Object.hasOwn(loaded, key)) {
|
|
147
|
+
return [key, loaded[key]];
|
|
148
|
+
}
|
|
149
|
+
return [key, undefined];
|
|
150
|
+
}));
|
|
151
|
+
const missingKeys = keySet.filter((key) => typeof next[key] !== "string" || !next[key]);
|
|
152
|
+
if (missingKeys.length) {
|
|
153
|
+
throw new SecretLoadError("MISSING_SECRETS", `Missing secrets: ${missingKeys.join(", ")}`);
|
|
154
|
+
}
|
|
155
|
+
let nextParsed;
|
|
156
|
+
try {
|
|
157
|
+
nextParsed = options.schema ? await options.schema.parseAsync({ ...next }) : { ...next };
|
|
158
|
+
nextParsed = structuredClone(nextParsed);
|
|
159
|
+
} catch {
|
|
160
|
+
throw new SecretLoadError("VALIDATION", "Secret schema validation failed. Check the configured schema; values are omitted.");
|
|
161
|
+
}
|
|
162
|
+
signal.throwIfAborted();
|
|
163
|
+
const nextSources = Object.fromEntries(keySet.map((key) => [
|
|
164
|
+
key,
|
|
165
|
+
Object.hasOwn(overrides, key) ? "process.env" : loaderSource
|
|
166
|
+
]));
|
|
167
|
+
if (options.writeToProcessEnv !== false && environmentVariables) {
|
|
168
|
+
let writes = environmentWrites.get(environmentVariables);
|
|
169
|
+
if (!writes) {
|
|
170
|
+
writes = new Map;
|
|
171
|
+
environmentWrites.set(environmentVariables, writes);
|
|
172
|
+
}
|
|
173
|
+
for (const key of keySet) {
|
|
174
|
+
if (Object.hasOwn(overrides, key)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
environmentVariables[key] = next[key];
|
|
178
|
+
writes.set(key, next[key]);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
cached = next;
|
|
182
|
+
parsed = nextParsed;
|
|
183
|
+
sources = nextSources;
|
|
184
|
+
};
|
|
185
|
+
const load = (request = {}) => {
|
|
186
|
+
if (inFlight) {
|
|
187
|
+
return inFlight;
|
|
188
|
+
}
|
|
189
|
+
inFlight = (async () => {
|
|
190
|
+
const start = Date.now();
|
|
191
|
+
try {
|
|
192
|
+
await withSecretDeadline({
|
|
193
|
+
timeoutMs: request.timeoutMs ?? options.timeoutMs,
|
|
194
|
+
signal: request.signal ?? options.signal
|
|
195
|
+
}, (signal) => loadSnapshot(request, signal));
|
|
196
|
+
const sourceCounts = new Map;
|
|
197
|
+
for (const key of keySet) {
|
|
198
|
+
const source = sources[key];
|
|
199
|
+
sourceCounts.set(source, (sourceCounts.get(source) ?? 0) + 1);
|
|
200
|
+
}
|
|
201
|
+
const counts = Object.fromEntries(sourceCounts);
|
|
202
|
+
log("info", "Loaded secrets", {
|
|
203
|
+
environment,
|
|
204
|
+
count: keySet.length,
|
|
205
|
+
durationMs: Date.now() - start,
|
|
206
|
+
source: Object.keys(counts).join(" + ") || "none",
|
|
207
|
+
sources: counts
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
const safe = error instanceof SecretLoadError ? error : new SecretLoadError("LOADER", "Secret loading failed.");
|
|
211
|
+
log("error", "Secret loading failed", {
|
|
212
|
+
environment,
|
|
213
|
+
count: keySet.length,
|
|
214
|
+
durationMs: Date.now() - start,
|
|
215
|
+
code: safe.code
|
|
216
|
+
});
|
|
217
|
+
throw safe;
|
|
218
|
+
}
|
|
219
|
+
})().finally(() => {
|
|
220
|
+
inFlight = undefined;
|
|
221
|
+
});
|
|
222
|
+
return inFlight;
|
|
223
|
+
};
|
|
224
|
+
const secretSet = {
|
|
225
|
+
secret(key) {
|
|
226
|
+
if (!Object.hasOwn(cached, key)) {
|
|
227
|
+
throw new SecretLoadError("MISSING_SECRETS", `Secret ${key} not found`);
|
|
228
|
+
}
|
|
229
|
+
return cached[key];
|
|
230
|
+
},
|
|
231
|
+
secrets: () => ({ ...cached }),
|
|
232
|
+
parsed: () => structuredClone(parsed),
|
|
233
|
+
sources: () => ({ ...sources }),
|
|
234
|
+
async reload(request) {
|
|
235
|
+
await load(request);
|
|
236
|
+
return secretSet;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
await load();
|
|
240
|
+
return secretSet;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/secrets/infisical.ts
|
|
244
|
+
import { z as z2 } from "zod";
|
|
1
245
|
// src/api-wrapper/headers.ts
|
|
2
246
|
var CANONICAL_HEADER_NAMES = {
|
|
3
247
|
"content-type": "Content-Type",
|
|
@@ -38,13 +282,16 @@ function hasJsonContentType(headers) {
|
|
|
38
282
|
const ct = getHeader(headers, "content-type") ?? "";
|
|
39
283
|
return ct.includes("application/json");
|
|
40
284
|
}
|
|
41
|
-
function buildFetchBody(body, headers) {
|
|
285
|
+
function buildFetchBody(body, headers, configError) {
|
|
42
286
|
if (body === undefined || body === null)
|
|
43
287
|
return;
|
|
44
288
|
if (body instanceof FormData || body instanceof Blob)
|
|
45
289
|
return body;
|
|
46
290
|
if (hasJsonContentType(headers))
|
|
47
291
|
return JSON.stringify(body);
|
|
292
|
+
if (typeof body === "object" && (Object.getPrototypeOf(body) === Object.prototype || Object.getPrototypeOf(body) === null) && configError) {
|
|
293
|
+
throw configError("Request body is a plain object but Content-Type is not application/json");
|
|
294
|
+
}
|
|
48
295
|
return body;
|
|
49
296
|
}
|
|
50
297
|
function getHeadersAsObject(headers) {
|
|
@@ -339,39 +586,61 @@ var isNetworkError = (e) => e instanceof NetworkError;
|
|
|
339
586
|
var isRequestAbortedError = (e) => e instanceof RequestAbortedError;
|
|
340
587
|
var isConfigError = (e) => e instanceof ConfigError;
|
|
341
588
|
var isActionError = (e) => e instanceof ActionError;
|
|
589
|
+
function isNonRetryableApiError(error) {
|
|
590
|
+
return error instanceof HttpResponseError || error instanceof ResponseValidationError || error instanceof RequestValidationError || error instanceof ConfigError || error instanceof RequestAbortedError;
|
|
591
|
+
}
|
|
342
592
|
// src/api-wrapper/builders.ts
|
|
343
593
|
import { ZodError as ZodError2 } from "zod";
|
|
344
|
-
function
|
|
594
|
+
function formatQueryString(queryParams, doNotEncode) {
|
|
595
|
+
if (Object.keys(queryParams).length === 0)
|
|
596
|
+
return "";
|
|
597
|
+
return doNotEncode ? Object.entries(queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(queryParams).toString();
|
|
598
|
+
}
|
|
599
|
+
function buildUrl(options) {
|
|
600
|
+
const {
|
|
601
|
+
endpoint,
|
|
602
|
+
params,
|
|
603
|
+
queryParams,
|
|
604
|
+
baseApiUrls,
|
|
605
|
+
defaultBaseApiUrl,
|
|
606
|
+
url,
|
|
607
|
+
doNotEncodeQueryParams,
|
|
608
|
+
configError
|
|
609
|
+
} = options;
|
|
610
|
+
let finalUrl;
|
|
345
611
|
if (url) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
throw configError(`Unknown base API URL: ${baseUrlKey}`);
|
|
612
|
+
finalUrl = replacePathParams(url, params, configError);
|
|
613
|
+
} else {
|
|
614
|
+
const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
|
|
615
|
+
if (!defaultBaseUrl) {
|
|
616
|
+
throw configError("At least one base API URL is required");
|
|
617
|
+
}
|
|
618
|
+
const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
|
|
619
|
+
const configBaseUrl = baseApiUrls[baseUrlKey];
|
|
620
|
+
if (!configBaseUrl) {
|
|
621
|
+
throw configError(`Unknown base API URL: ${baseUrlKey}`);
|
|
622
|
+
}
|
|
623
|
+
const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
|
|
624
|
+
const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
|
|
625
|
+
finalUrl = `${baseUrl}${replacePathParams(path, params, configError)}`;
|
|
361
626
|
}
|
|
362
|
-
const
|
|
363
|
-
const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
|
|
364
|
-
const finalUrl = replacePathParams(`${baseUrl}${path}`, params, configError);
|
|
365
|
-
const queryString = new URLSearchParams(buildQueryParamsRaw(endpoint, reqQuery)).toString();
|
|
627
|
+
const queryString = formatQueryString(queryParams, doNotEncodeQueryParams);
|
|
366
628
|
return {
|
|
367
629
|
finalUrl,
|
|
368
630
|
fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
|
|
369
631
|
};
|
|
370
632
|
}
|
|
371
|
-
function
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
633
|
+
function buildPathParams(endpoint, params, ctx) {
|
|
634
|
+
if (!endpoint.reqParamsSchema)
|
|
635
|
+
return params;
|
|
636
|
+
try {
|
|
637
|
+
return endpoint.reqParamsSchema.parse(params);
|
|
638
|
+
} catch (error) {
|
|
639
|
+
if (error instanceof ZodError2) {
|
|
640
|
+
throw new RequestValidationError(error, "params", params, ctx());
|
|
641
|
+
}
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
375
644
|
}
|
|
376
645
|
function buildQueryParams(endpoint, query, ctx) {
|
|
377
646
|
const defaultParams = endpoint.reqDefaultQueryParams ?? {};
|
|
@@ -504,8 +773,8 @@ function createActionCache() {
|
|
|
504
773
|
function makeDefineAction() {
|
|
505
774
|
function defineAction(...args) {
|
|
506
775
|
if (args.length === 0) {
|
|
507
|
-
return (
|
|
508
|
-
...
|
|
776
|
+
return (def) => ({
|
|
777
|
+
...def,
|
|
509
778
|
noRuntimeInput: false
|
|
510
779
|
});
|
|
511
780
|
}
|
|
@@ -536,6 +805,7 @@ var SENSITIVE_BODY_KEYS = new Set([
|
|
|
536
805
|
"refresh_token",
|
|
537
806
|
"secret",
|
|
538
807
|
"client_secret",
|
|
808
|
+
"clientsecret",
|
|
539
809
|
"apikey",
|
|
540
810
|
"api_key"
|
|
541
811
|
]);
|
|
@@ -626,16 +896,14 @@ async function validateResponse(schema, data, ctx) {
|
|
|
626
896
|
}
|
|
627
897
|
}
|
|
628
898
|
async function httpRequest(config, ctx) {
|
|
629
|
-
const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
|
|
630
|
-
const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
|
|
631
899
|
const timeoutController = new AbortController;
|
|
632
900
|
const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => timeoutController.abort(), config.timeoutMs);
|
|
633
901
|
const signal = mergeSignals(timeoutController.signal, config.userSignal);
|
|
634
902
|
try {
|
|
635
|
-
return await config.fetchFn(fullUrl, {
|
|
903
|
+
return await config.fetchFn(config.fullUrl, {
|
|
636
904
|
method: config.method,
|
|
637
905
|
headers: config.headers,
|
|
638
|
-
body: buildFetchBody(config.body, config.headers),
|
|
906
|
+
body: buildFetchBody(config.body, config.headers, (msg) => new ConfigError(msg, ctx())),
|
|
639
907
|
signal
|
|
640
908
|
});
|
|
641
909
|
} catch (error) {
|
|
@@ -648,6 +916,8 @@ async function httpRequest(config, ctx) {
|
|
|
648
916
|
}
|
|
649
917
|
throw new RequestAbortedError(ctx(), error);
|
|
650
918
|
}
|
|
919
|
+
if (error instanceof ConfigError)
|
|
920
|
+
throw error;
|
|
651
921
|
throw new NetworkError(error instanceof Error ? error.message : String(error), ctx(), error);
|
|
652
922
|
} finally {
|
|
653
923
|
if (timeout)
|
|
@@ -684,35 +954,26 @@ async function readErrorResponse(response) {
|
|
|
684
954
|
};
|
|
685
955
|
}
|
|
686
956
|
function getRetryDelay(params) {
|
|
957
|
+
const maxDelay = params.maxRetryDelayMs ?? 30000;
|
|
687
958
|
const { retryDelay, attempt, response } = params;
|
|
688
959
|
if (response && (response.status === 429 || response.status === 503)) {
|
|
689
960
|
const retryAfter = response.headers.get("retry-after");
|
|
690
961
|
if (retryAfter) {
|
|
691
962
|
const seconds = Number(retryAfter);
|
|
692
|
-
if (Number.isFinite(seconds))
|
|
693
|
-
return seconds * 1000;
|
|
963
|
+
if (Number.isFinite(seconds)) {
|
|
964
|
+
return Math.min(Math.max(0, seconds * 1000), maxDelay);
|
|
965
|
+
}
|
|
694
966
|
const dateMs = Date.parse(retryAfter);
|
|
695
967
|
if (!Number.isNaN(dateMs)) {
|
|
696
|
-
return Math.max(0, dateMs - Date.now());
|
|
968
|
+
return Math.min(Math.max(0, dateMs - Date.now()), maxDelay);
|
|
697
969
|
}
|
|
698
970
|
}
|
|
699
971
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
return retryDelay;
|
|
972
|
+
const delayMs = typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay;
|
|
973
|
+
return Math.min(delayMs, maxDelay);
|
|
703
974
|
}
|
|
704
975
|
function shouldAttemptRetryOnError(error) {
|
|
705
|
-
|
|
706
|
-
return false;
|
|
707
|
-
if (error instanceof ResponseValidationError)
|
|
708
|
-
return false;
|
|
709
|
-
if (error instanceof RequestValidationError)
|
|
710
|
-
return false;
|
|
711
|
-
if (error instanceof ConfigError)
|
|
712
|
-
return false;
|
|
713
|
-
if (error instanceof RequestAbortedError)
|
|
714
|
-
return false;
|
|
715
|
-
return true;
|
|
976
|
+
return !isNonRetryableApiError(error);
|
|
716
977
|
}
|
|
717
978
|
function shouldAttemptRetryOnHttp(retryCtx) {
|
|
718
979
|
const { error, response } = retryCtx;
|
|
@@ -725,6 +986,7 @@ async function makeRequestWithRetry(args) {
|
|
|
725
986
|
config,
|
|
726
987
|
maxRetries,
|
|
727
988
|
retryDelay,
|
|
989
|
+
maxRetryDelayMs,
|
|
728
990
|
validateFn,
|
|
729
991
|
shouldRetry,
|
|
730
992
|
logger,
|
|
@@ -763,7 +1025,8 @@ async function makeRequestWithRetry(args) {
|
|
|
763
1025
|
const delayMs = getRetryDelay({
|
|
764
1026
|
retryDelay,
|
|
765
1027
|
attempt: retryCount,
|
|
766
|
-
response
|
|
1028
|
+
response,
|
|
1029
|
+
maxRetryDelayMs
|
|
767
1030
|
});
|
|
768
1031
|
await onRetry?.({
|
|
769
1032
|
...retryCtx,
|
|
@@ -778,12 +1041,12 @@ async function makeRequestWithRetry(args) {
|
|
|
778
1041
|
throw new HttpResponseError(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
|
|
779
1042
|
}
|
|
780
1043
|
return {
|
|
781
|
-
validatedData: await validateFn(response),
|
|
1044
|
+
validatedData: await validateFn(response, retryCount),
|
|
782
1045
|
retryCount,
|
|
783
1046
|
httpStatus: response.status
|
|
784
1047
|
};
|
|
785
1048
|
} catch (error) {
|
|
786
|
-
if (error
|
|
1049
|
+
if (isNonRetryableApiError(error)) {
|
|
787
1050
|
throw error;
|
|
788
1051
|
}
|
|
789
1052
|
const retryCtx = {
|
|
@@ -800,7 +1063,8 @@ async function makeRequestWithRetry(args) {
|
|
|
800
1063
|
const delayMs = getRetryDelay({
|
|
801
1064
|
retryDelay,
|
|
802
1065
|
attempt: retryCount,
|
|
803
|
-
response: undefined
|
|
1066
|
+
response: undefined,
|
|
1067
|
+
maxRetryDelayMs
|
|
804
1068
|
});
|
|
805
1069
|
await onRetry?.({
|
|
806
1070
|
...retryCtx,
|
|
@@ -814,19 +1078,21 @@ async function makeRequestWithRetry(args) {
|
|
|
814
1078
|
}
|
|
815
1079
|
async function validateAndTransformResponse(endpoint, response, ctx) {
|
|
816
1080
|
const responseHeaders = getHeadersAsObject(response.headers);
|
|
1081
|
+
const text = await response.text();
|
|
1082
|
+
const responseSizeBytes = new TextEncoder().encode(text).length;
|
|
817
1083
|
let responseData;
|
|
818
1084
|
if (hasJsonContentType(responseHeaders)) {
|
|
819
|
-
const text = await response.text();
|
|
820
1085
|
try {
|
|
821
1086
|
responseData = text === "" ? undefined : JSON.parse(text);
|
|
822
1087
|
} catch (error) {
|
|
823
1088
|
throw new ResponseValidationError(makeJsonParseZodError(error, text), text, ctx());
|
|
824
1089
|
}
|
|
825
1090
|
} else {
|
|
826
|
-
responseData =
|
|
1091
|
+
responseData = text;
|
|
827
1092
|
}
|
|
828
1093
|
const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
|
|
829
|
-
|
|
1094
|
+
const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
|
|
1095
|
+
return { data, responseSizeBytes };
|
|
830
1096
|
}
|
|
831
1097
|
function defaultShouldRetry(ctx) {
|
|
832
1098
|
if (ctx.error)
|
|
@@ -845,6 +1111,28 @@ function isActionInvokeOptionsOnly(value) {
|
|
|
845
1111
|
return false;
|
|
846
1112
|
return Object.keys(value).every((k) => k === "signal" || k === "meta");
|
|
847
1113
|
}
|
|
1114
|
+
function resolveActionInvocation(action, actionName, first, second) {
|
|
1115
|
+
if (!action.noRuntimeInput) {
|
|
1116
|
+
return { input: first, options: second ?? {} };
|
|
1117
|
+
}
|
|
1118
|
+
if (first === undefined || first === null) {
|
|
1119
|
+
return { input: undefined, options: second ?? {} };
|
|
1120
|
+
}
|
|
1121
|
+
if (second !== undefined) {
|
|
1122
|
+
return { input: undefined, options: second };
|
|
1123
|
+
}
|
|
1124
|
+
if (isActionInvokeOptionsOnly(first)) {
|
|
1125
|
+
return { input: undefined, options: first };
|
|
1126
|
+
}
|
|
1127
|
+
throw new ConfigError(`Action "${actionName}" does not accept input; pass only optional { signal, meta }`, {
|
|
1128
|
+
endpoint: actionName,
|
|
1129
|
+
method: "GET",
|
|
1130
|
+
url: "",
|
|
1131
|
+
attempt: 0,
|
|
1132
|
+
maxRetries: 0,
|
|
1133
|
+
elapsedMs: 0
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
848
1136
|
function createApiClientImpl(apiConfig) {
|
|
849
1137
|
let actions = {};
|
|
850
1138
|
const cache = createActionCache();
|
|
@@ -873,22 +1161,12 @@ function createApiClientImpl(apiConfig) {
|
|
|
873
1161
|
throw await reportApiError(new ConfigError(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
|
|
874
1162
|
}
|
|
875
1163
|
baseContext.method = endpoint.method;
|
|
876
|
-
let
|
|
877
|
-
let fullUrl;
|
|
878
|
-
try {
|
|
879
|
-
const built = buildUrl(endpoint, rawInput.reqParams, rawInput.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url, (msg) => new ConfigError(msg, ctx()));
|
|
880
|
-
finalUrl = built.finalUrl;
|
|
881
|
-
fullUrl = built.fullUrl;
|
|
882
|
-
} catch (e) {
|
|
883
|
-
if (e instanceof ApiError)
|
|
884
|
-
throw await reportApiError(e, apiConfig);
|
|
885
|
-
throw e;
|
|
886
|
-
}
|
|
887
|
-
baseContext.url = fullUrl;
|
|
1164
|
+
let pathParams;
|
|
888
1165
|
let queryParams;
|
|
889
1166
|
let body;
|
|
890
1167
|
let headers;
|
|
891
1168
|
try {
|
|
1169
|
+
pathParams = buildPathParams(endpoint, rawInput.reqParams, ctx);
|
|
892
1170
|
queryParams = buildQueryParams(endpoint, rawInput.reqQuery, ctx);
|
|
893
1171
|
body = buildRequestBody(endpoint, rawInput.reqBody, ctx);
|
|
894
1172
|
headers = buildHeaders(endpoint, rawInput.reqHeaders, apiConfig.defaultHeaders ?? {}, ctx);
|
|
@@ -897,6 +1175,25 @@ function createApiClientImpl(apiConfig) {
|
|
|
897
1175
|
throw await reportApiError(e, apiConfig);
|
|
898
1176
|
throw e;
|
|
899
1177
|
}
|
|
1178
|
+
let fullUrl;
|
|
1179
|
+
try {
|
|
1180
|
+
const built = buildUrl({
|
|
1181
|
+
endpoint,
|
|
1182
|
+
params: pathParams,
|
|
1183
|
+
queryParams,
|
|
1184
|
+
baseApiUrls: apiConfig.baseApiUrls,
|
|
1185
|
+
defaultBaseApiUrl: apiConfig.defaultBaseApiUrl,
|
|
1186
|
+
url: input.url,
|
|
1187
|
+
doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
|
|
1188
|
+
configError: (msg) => new ConfigError(msg, ctx())
|
|
1189
|
+
});
|
|
1190
|
+
fullUrl = built.fullUrl;
|
|
1191
|
+
} catch (e) {
|
|
1192
|
+
if (e instanceof ApiError)
|
|
1193
|
+
throw await reportApiError(e, apiConfig);
|
|
1194
|
+
throw e;
|
|
1195
|
+
}
|
|
1196
|
+
baseContext.url = fullUrl;
|
|
900
1197
|
baseContext.requestHeaders = headers;
|
|
901
1198
|
baseContext.requestBody = body;
|
|
902
1199
|
if (apiConfig.onRequest) {
|
|
@@ -921,11 +1218,9 @@ function createApiClientImpl(apiConfig) {
|
|
|
921
1218
|
}
|
|
922
1219
|
const config = {
|
|
923
1220
|
method: endpoint.method,
|
|
924
|
-
|
|
925
|
-
queryParams,
|
|
1221
|
+
fullUrl,
|
|
926
1222
|
body,
|
|
927
1223
|
headers,
|
|
928
|
-
doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
|
|
929
1224
|
timeoutMs: input.timeoutMs ?? apiConfig.timeoutMs,
|
|
930
1225
|
fetchFn: input.fetch ?? apiConfig.fetch ?? fetch,
|
|
931
1226
|
userSignal: input.signal
|
|
@@ -935,7 +1230,8 @@ function createApiClientImpl(apiConfig) {
|
|
|
935
1230
|
config,
|
|
936
1231
|
maxRetries,
|
|
937
1232
|
retryDelay: apiConfig.retryDelayMs ?? 300,
|
|
938
|
-
|
|
1233
|
+
maxRetryDelayMs: apiConfig.maxRetryDelayMs,
|
|
1234
|
+
validateFn: (response, attempt) => validateAndTransformResponse(endpoint, response, (over) => ctx({ attempt, ...over })),
|
|
939
1235
|
shouldRetry: apiConfig.shouldRetry ?? defaultShouldRetry,
|
|
940
1236
|
logger: apiConfig.logger,
|
|
941
1237
|
ctx,
|
|
@@ -947,10 +1243,10 @@ function createApiClientImpl(apiConfig) {
|
|
|
947
1243
|
onRetry: apiConfig.onRetry
|
|
948
1244
|
});
|
|
949
1245
|
const responseTimeMs = elapsed();
|
|
950
|
-
const responseSizeMb =
|
|
1246
|
+
const responseSizeMb = result.validatedData.responseSizeBytes / (1024 * 1024);
|
|
951
1247
|
const requestBodySizeMb = calculateSizeInMb(config.body);
|
|
952
1248
|
return {
|
|
953
|
-
data: result.validatedData,
|
|
1249
|
+
data: result.validatedData.data,
|
|
954
1250
|
requestBodySizeMb,
|
|
955
1251
|
responseSizeMb,
|
|
956
1252
|
responseTimeMs,
|
|
@@ -1006,22 +1302,14 @@ function createApiClientImpl(apiConfig) {
|
|
|
1006
1302
|
throw await reportApiError(wrapped, apiConfig);
|
|
1007
1303
|
}
|
|
1008
1304
|
}
|
|
1305
|
+
async function dispatchAction(name, first, second) {
|
|
1306
|
+
const action = actions[name];
|
|
1307
|
+
const { input, options } = resolveActionInvocation(action, name, first, second);
|
|
1308
|
+
return invokeAction(name, input, options);
|
|
1309
|
+
}
|
|
1009
1310
|
const request = async (key, input, options) => {
|
|
1010
1311
|
if (key in actions) {
|
|
1011
|
-
|
|
1012
|
-
if (action.noRuntimeInput) {
|
|
1013
|
-
if (input === undefined || input === null) {
|
|
1014
|
-
return invokeAction(key, undefined, options ?? {});
|
|
1015
|
-
}
|
|
1016
|
-
if (options !== undefined) {
|
|
1017
|
-
return invokeAction(key, undefined, options);
|
|
1018
|
-
}
|
|
1019
|
-
if (isActionInvokeOptionsOnly(input)) {
|
|
1020
|
-
return invokeAction(key, undefined, input);
|
|
1021
|
-
}
|
|
1022
|
-
return invokeAction(key, undefined, {});
|
|
1023
|
-
}
|
|
1024
|
-
return invokeAction(key, input, options);
|
|
1312
|
+
return dispatchAction(key, input, options);
|
|
1025
1313
|
}
|
|
1026
1314
|
return invokeEndpoint(key, input ?? {});
|
|
1027
1315
|
};
|
|
@@ -1035,22 +1323,7 @@ function createApiClientImpl(apiConfig) {
|
|
|
1035
1323
|
return (input) => invokeEndpoint(prop, input ?? {});
|
|
1036
1324
|
}
|
|
1037
1325
|
if (prop in actions) {
|
|
1038
|
-
|
|
1039
|
-
if (action.noRuntimeInput) {
|
|
1040
|
-
return (first, second) => {
|
|
1041
|
-
if (first === undefined || first === null) {
|
|
1042
|
-
return invokeAction(prop, undefined, second ?? {});
|
|
1043
|
-
}
|
|
1044
|
-
if (second !== undefined) {
|
|
1045
|
-
return invokeAction(prop, undefined, second);
|
|
1046
|
-
}
|
|
1047
|
-
if (isActionInvokeOptionsOnly(first)) {
|
|
1048
|
-
return invokeAction(prop, undefined, first);
|
|
1049
|
-
}
|
|
1050
|
-
return invokeAction(prop, undefined, {});
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
return (input, opt) => invokeAction(prop, input, opt);
|
|
1326
|
+
return (first, second) => dispatchAction(prop, first, second);
|
|
1054
1327
|
}
|
|
1055
1328
|
return;
|
|
1056
1329
|
}
|
|
@@ -1078,72 +1351,227 @@ function createApiClientImpl(apiConfig) {
|
|
|
1078
1351
|
return proxy;
|
|
1079
1352
|
}
|
|
1080
1353
|
var createApiClient = (apiConfig) => createApiClientImpl(apiConfig);
|
|
1081
|
-
// src/secrets/infisical.ts
|
|
1354
|
+
// src/secrets/infisical-cli.ts
|
|
1355
|
+
async function readInfisicalCliToken(options) {
|
|
1356
|
+
const { execFile } = await import("node:child_process").catch(() => {
|
|
1357
|
+
throw new SecretLoadError("CLI", "Infisical CLI fallback requires a runtime with subprocess support. Provide Infisical client credentials or a custom secret loader.");
|
|
1358
|
+
});
|
|
1359
|
+
options.signal.throwIfAborted();
|
|
1360
|
+
const env = {
|
|
1361
|
+
...getProcessEnvironment(),
|
|
1362
|
+
INFISICAL_DISABLE_UPDATE_CHECK: "true"
|
|
1363
|
+
};
|
|
1364
|
+
delete env.INFISICAL_TOKEN;
|
|
1365
|
+
delete env.INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN;
|
|
1366
|
+
delete env.TOKEN;
|
|
1367
|
+
try {
|
|
1368
|
+
const stdout = await new Promise((resolve, reject) => {
|
|
1369
|
+
const child = execFile("infisical", [
|
|
1370
|
+
"user",
|
|
1371
|
+
"get",
|
|
1372
|
+
"token",
|
|
1373
|
+
"--plain",
|
|
1374
|
+
`--domain=${options.siteUrl}`,
|
|
1375
|
+
"--silent"
|
|
1376
|
+
], {
|
|
1377
|
+
env,
|
|
1378
|
+
encoding: "utf8",
|
|
1379
|
+
shell: false,
|
|
1380
|
+
timeout: options.timeoutMs ?? 30000,
|
|
1381
|
+
signal: options.signal,
|
|
1382
|
+
killSignal: "SIGKILL",
|
|
1383
|
+
maxBuffer: 10485760,
|
|
1384
|
+
windowsHide: true
|
|
1385
|
+
}, (error, output) => {
|
|
1386
|
+
if (error) {
|
|
1387
|
+
reject(error);
|
|
1388
|
+
} else {
|
|
1389
|
+
resolve(output);
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
child.stdin?.end();
|
|
1393
|
+
});
|
|
1394
|
+
return stdout.trim();
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
if (options.signal.aborted) {
|
|
1397
|
+
throw options.signal.reason;
|
|
1398
|
+
}
|
|
1399
|
+
const failure = error;
|
|
1400
|
+
if (failure?.code === "ENOENT") {
|
|
1401
|
+
throw new SecretLoadError("CLI", "Infisical CLI not found. Install infisical and ensure it is on PATH, or provide Infisical client credentials.");
|
|
1402
|
+
}
|
|
1403
|
+
if (failure?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
1404
|
+
throw new SecretLoadError("CLI", "Infisical CLI output exceeded the 10 MiB limit.");
|
|
1405
|
+
}
|
|
1406
|
+
if (failure?.killed) {
|
|
1407
|
+
throw new SecretLoadError("TIMEOUT", `Infisical CLI timed out after ${(options.timeoutMs ?? 30000) / 1000} seconds or was terminated.`);
|
|
1408
|
+
}
|
|
1409
|
+
throw new SecretLoadError("CLI", `Infisical CLI session token failed. Run infisical login --domain=${options.siteUrl} and verify access to the configured organization, project, and environment.`);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/secrets/infisical-config.ts
|
|
1414
|
+
function validateInfisicalScope(options) {
|
|
1415
|
+
validateInfisicalOrganizationId(options.organizationId);
|
|
1416
|
+
let url;
|
|
1417
|
+
try {
|
|
1418
|
+
url = new URL(options.siteUrl);
|
|
1419
|
+
} catch {
|
|
1420
|
+
throw new SecretLoadError("CONFIGURATION", "Invalid Infisical siteUrl.");
|
|
1421
|
+
}
|
|
1422
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
1423
|
+
throw new SecretLoadError("CONFIGURATION", "Infisical siteUrl must be an HTTP(S) URL without credentials, query, or fragment.");
|
|
1424
|
+
}
|
|
1425
|
+
if (!options.projectId.trim() || !options.environment.trim() || !(options.secretPath ?? "/").startsWith("/")) {
|
|
1426
|
+
throw new SecretLoadError("CONFIGURATION", "Infisical requires a projectId, environment, and absolute secretPath.");
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
function validateInfisicalOrganizationId(organizationId) {
|
|
1430
|
+
if (typeof organizationId !== "string" || !organizationId.trim() || /\s/.test(organizationId)) {
|
|
1431
|
+
throw new SecretLoadError("CONFIGURATION", "Infisical organizationId is required and must be a nonempty ID without whitespace.");
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// src/secrets/infisical-session.ts
|
|
1082
1436
|
import { z } from "zod";
|
|
1437
|
+
var sessionClaimsSchema = z.object({
|
|
1438
|
+
organizationId: z.string().optional(),
|
|
1439
|
+
subOrganizationId: z.string().optional(),
|
|
1440
|
+
exp: z.number().finite().optional()
|
|
1441
|
+
});
|
|
1442
|
+
function parseInfisicalSessionToken(token) {
|
|
1443
|
+
let claims;
|
|
1444
|
+
try {
|
|
1445
|
+
if (!/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) {
|
|
1446
|
+
throw new Error("Invalid token format");
|
|
1447
|
+
}
|
|
1448
|
+
const payload = token.split(".")[1];
|
|
1449
|
+
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
1450
|
+
claims = sessionClaimsSchema.parse(JSON.parse(json));
|
|
1451
|
+
} catch {
|
|
1452
|
+
throw new SecretLoadError("AUTHENTICATION", "Infisical returned an invalid user session token. Run infisical login for the configured domain.");
|
|
1453
|
+
}
|
|
1454
|
+
if (claims.exp !== undefined && claims.exp <= Date.now() / 1000) {
|
|
1455
|
+
throw new SecretLoadError("AUTHENTICATION", "Infisical user session has expired. Run infisical login for the configured domain.");
|
|
1456
|
+
}
|
|
1457
|
+
return { organizationId: claims.subOrganizationId ?? claims.organizationId };
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// src/secrets/infisical.ts
|
|
1083
1461
|
var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
|
|
1084
1462
|
var defaultFetch = globalThis.fetch;
|
|
1085
|
-
var infisicalAuthResponseSchema =
|
|
1086
|
-
|
|
1463
|
+
var infisicalAuthResponseSchema = z2.object({ accessToken: z2.string() });
|
|
1464
|
+
var infisicalSecretSchema = z2.object({
|
|
1465
|
+
secretKey: z2.string(),
|
|
1466
|
+
secretValue: z2.string()
|
|
1087
1467
|
});
|
|
1088
|
-
var
|
|
1089
|
-
|
|
1090
|
-
secretValue: z.string()
|
|
1468
|
+
var infisicalSecretsResponseSchema = z2.object({
|
|
1469
|
+
secrets: z2.array(infisicalSecretSchema)
|
|
1091
1470
|
});
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1471
|
+
async function fetchInfisicalSecrets(options) {
|
|
1472
|
+
validateInfisicalScope(options);
|
|
1473
|
+
return withSecretDeadline(options, async (signal) => {
|
|
1474
|
+
let operation = "login";
|
|
1475
|
+
try {
|
|
1476
|
+
const infisical = createInfisicalApiClient(options.siteUrl);
|
|
1477
|
+
const { data: auth } = await infisical.request("login", {
|
|
1478
|
+
signal,
|
|
1479
|
+
reqBody: {
|
|
1480
|
+
clientId: options.clientId,
|
|
1481
|
+
clientSecret: options.clientSecret
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
signal.throwIfAborted();
|
|
1485
|
+
operation = "listSecrets";
|
|
1486
|
+
return await listInfisicalSecrets(infisical, auth.accessToken, {
|
|
1487
|
+
...options,
|
|
1488
|
+
signal
|
|
1489
|
+
});
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
throwInfisicalHttpError(error, operation, signal);
|
|
1108
1492
|
}
|
|
1109
1493
|
});
|
|
1494
|
+
}
|
|
1495
|
+
async function loadInfisicalSecrets(options) {
|
|
1496
|
+
validateInfisicalScope(options);
|
|
1497
|
+
const env = getProcessEnvironment();
|
|
1498
|
+
const clientId = env?.[options.clientIdEnvKey];
|
|
1499
|
+
const clientSecret = env?.[options.clientSecretEnvKey];
|
|
1500
|
+
const mode = options.auth ?? "auto";
|
|
1501
|
+
if (!["auto", "http", "cli"].includes(mode)) {
|
|
1502
|
+
throw new SecretLoadError("CONFIGURATION", "Invalid Infisical auth mode.");
|
|
1503
|
+
}
|
|
1504
|
+
const useHttp = mode === "http" || mode === "auto" && Boolean(clientId && clientSecret);
|
|
1505
|
+
if (useHttp) {
|
|
1506
|
+
if (!clientId || !clientSecret) {
|
|
1507
|
+
throw new SecretLoadError("AUTHENTICATION", "Infisical HTTP authentication requires both client credentials.");
|
|
1508
|
+
}
|
|
1509
|
+
options.reportSource?.("Infisical HTTP");
|
|
1510
|
+
return fetchInfisicalSecrets({ ...options, clientId, clientSecret });
|
|
1511
|
+
}
|
|
1512
|
+
options.reportSource?.(`Infisical CLI session (organization=${options.organizationId} project=${options.projectId} environment=${options.environment})`);
|
|
1513
|
+
return withSecretDeadline(options, (signal) => fetchInfisicalSessionSecrets({ ...options, signal }));
|
|
1514
|
+
}
|
|
1515
|
+
async function fetchInfisicalSessionSecrets(options) {
|
|
1516
|
+
const { signal } = options;
|
|
1517
|
+
let token = await readInfisicalCliToken(options);
|
|
1518
|
+
signal.throwIfAborted();
|
|
1519
|
+
const session = parseInfisicalSessionToken(token);
|
|
1520
|
+
const infisical = createInfisicalApiClient(options.siteUrl);
|
|
1521
|
+
let operation = "selectOrganization";
|
|
1522
|
+
try {
|
|
1523
|
+
if (session.organizationId !== options.organizationId) {
|
|
1524
|
+
const { data } = await infisical.request("selectOrganization", {
|
|
1525
|
+
signal,
|
|
1526
|
+
reqHeaders: {
|
|
1527
|
+
Authorization: `Bearer ${token}`,
|
|
1528
|
+
"User-Agent": "hanzio/secrets"
|
|
1529
|
+
},
|
|
1530
|
+
reqBody: { organizationId: options.organizationId }
|
|
1531
|
+
});
|
|
1532
|
+
signal.throwIfAborted();
|
|
1533
|
+
if (data.isMfaEnabled) {
|
|
1534
|
+
throw new SecretLoadError("AUTHENTICATION", "Infisical requires MFA for this organization. Complete infisical login for the target organization and configured domain before loading secrets.", { operation });
|
|
1535
|
+
}
|
|
1536
|
+
const scopedSession = parseInfisicalSessionToken(data.token);
|
|
1537
|
+
if (scopedSession.organizationId !== options.organizationId) {
|
|
1538
|
+
throw new SecretLoadError("AUTHENTICATION", "Infisical returned a session for a different organization.", { operation });
|
|
1539
|
+
}
|
|
1540
|
+
token = data.token;
|
|
1541
|
+
}
|
|
1542
|
+
signal.throwIfAborted();
|
|
1543
|
+
operation = "listSecrets";
|
|
1544
|
+
return await listInfisicalSecrets(infisical, token, options);
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
throwInfisicalHttpError(error, operation, signal);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
async function listInfisicalSecrets(infisical, token, options) {
|
|
1110
1550
|
const { data } = await infisical.request("listSecrets", {
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
},
|
|
1551
|
+
signal: options.signal,
|
|
1552
|
+
reqHeaders: { Authorization: `Bearer ${token}` },
|
|
1114
1553
|
reqQuery: {
|
|
1115
|
-
environment,
|
|
1116
|
-
projectId,
|
|
1117
|
-
secretPath: "/",
|
|
1554
|
+
environment: options.environment,
|
|
1555
|
+
projectId: options.projectId,
|
|
1556
|
+
secretPath: options.secretPath ?? "/",
|
|
1118
1557
|
viewSecretValue: true,
|
|
1119
1558
|
expandSecretReferences: true,
|
|
1120
1559
|
includeImports: true
|
|
1121
1560
|
}
|
|
1122
1561
|
});
|
|
1123
|
-
const wantedKeys = new Set(keys);
|
|
1562
|
+
const wantedKeys = new Set(options.keys);
|
|
1124
1563
|
return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
|
|
1125
1564
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
if (!clientId || !clientSecret) {
|
|
1137
|
-
throw new Error("Missing Infisical credentials");
|
|
1138
|
-
}
|
|
1139
|
-
return fetchInfisicalSecrets({
|
|
1140
|
-
keys,
|
|
1141
|
-
projectId,
|
|
1142
|
-
environment,
|
|
1143
|
-
siteUrl,
|
|
1144
|
-
clientId,
|
|
1145
|
-
clientSecret
|
|
1146
|
-
});
|
|
1565
|
+
function throwInfisicalHttpError(error, operation, signal) {
|
|
1566
|
+
if (signal.aborted) {
|
|
1567
|
+
throw signal.reason;
|
|
1568
|
+
}
|
|
1569
|
+
if (error instanceof SecretLoadError) {
|
|
1570
|
+
throw error;
|
|
1571
|
+
}
|
|
1572
|
+
const status = error instanceof HttpResponseError ? error.status : undefined;
|
|
1573
|
+
const guidance = operation === "selectOrganization" && (status === 401 || status === 403) ? " Run infisical login for the target organization and configured domain, then verify your access." : "";
|
|
1574
|
+
throw new SecretLoadError("HTTP", `Infisical HTTP ${operation} failed${status ? ` (status ${status})` : ""}.${guidance}`, { operation, status });
|
|
1147
1575
|
}
|
|
1148
1576
|
function createInfisicalApiClient(siteUrl) {
|
|
1149
1577
|
return createApiClient({
|
|
@@ -1155,12 +1583,26 @@ function createInfisicalApiClient(siteUrl) {
|
|
|
1155
1583
|
fetch: getFetch(),
|
|
1156
1584
|
retries: 1,
|
|
1157
1585
|
endpoints: {
|
|
1586
|
+
selectOrganization: {
|
|
1587
|
+
method: "POST",
|
|
1588
|
+
path: "/api/v3/auth/select-organization",
|
|
1589
|
+
reqHeadersSchema: z2.object({
|
|
1590
|
+
Authorization: z2.string(),
|
|
1591
|
+
"User-Agent": z2.string()
|
|
1592
|
+
}),
|
|
1593
|
+
reqBodySchema: z2.object({ organizationId: z2.string() }),
|
|
1594
|
+
resSchema: z2.object({
|
|
1595
|
+
token: z2.string().min(1),
|
|
1596
|
+
isMfaEnabled: z2.boolean().optional()
|
|
1597
|
+
}),
|
|
1598
|
+
reqBodyFormat: "json"
|
|
1599
|
+
},
|
|
1158
1600
|
login: {
|
|
1159
1601
|
method: "POST",
|
|
1160
1602
|
path: "/api/v1/auth/universal-auth/login",
|
|
1161
|
-
reqBodySchema:
|
|
1162
|
-
clientId:
|
|
1163
|
-
clientSecret:
|
|
1603
|
+
reqBodySchema: z2.object({
|
|
1604
|
+
clientId: z2.string(),
|
|
1605
|
+
clientSecret: z2.string()
|
|
1164
1606
|
}),
|
|
1165
1607
|
resSchema: infisicalAuthResponseSchema,
|
|
1166
1608
|
reqBodyFormat: "json"
|
|
@@ -1168,16 +1610,16 @@ function createInfisicalApiClient(siteUrl) {
|
|
|
1168
1610
|
listSecrets: {
|
|
1169
1611
|
method: "GET",
|
|
1170
1612
|
path: "/api/v4/secrets",
|
|
1171
|
-
reqHeadersSchema:
|
|
1172
|
-
Authorization:
|
|
1613
|
+
reqHeadersSchema: z2.object({
|
|
1614
|
+
Authorization: z2.string()
|
|
1173
1615
|
}),
|
|
1174
|
-
reqQuerySchema:
|
|
1175
|
-
projectId:
|
|
1176
|
-
environment:
|
|
1177
|
-
secretPath:
|
|
1178
|
-
viewSecretValue:
|
|
1179
|
-
expandSecretReferences:
|
|
1180
|
-
includeImports:
|
|
1616
|
+
reqQuerySchema: z2.object({
|
|
1617
|
+
projectId: z2.string(),
|
|
1618
|
+
environment: z2.string(),
|
|
1619
|
+
secretPath: z2.string(),
|
|
1620
|
+
viewSecretValue: z2.boolean(),
|
|
1621
|
+
expandSecretReferences: z2.boolean(),
|
|
1622
|
+
includeImports: z2.boolean()
|
|
1181
1623
|
}),
|
|
1182
1624
|
resSchema: infisicalSecretsResponseSchema
|
|
1183
1625
|
}
|
|
@@ -1185,21 +1627,25 @@ function createInfisicalApiClient(siteUrl) {
|
|
|
1185
1627
|
});
|
|
1186
1628
|
}
|
|
1187
1629
|
function getFetch() {
|
|
1188
|
-
|
|
1630
|
+
const fetchFn = globalThis.fetch ?? defaultFetch;
|
|
1631
|
+
return (input, init) => {
|
|
1632
|
+
init?.signal?.throwIfAborted();
|
|
1633
|
+
return fetchFn(input, init);
|
|
1634
|
+
};
|
|
1189
1635
|
}
|
|
1190
|
-
|
|
1191
|
-
// src/secrets/loaders.ts
|
|
1636
|
+
// src/secrets/worker-loader.ts
|
|
1192
1637
|
function cloudflareWorkerEnvLoader(workerEnv) {
|
|
1193
|
-
|
|
1194
|
-
const out =
|
|
1638
|
+
const loader = async (ctx) => {
|
|
1639
|
+
const out = Object.create(null);
|
|
1195
1640
|
for (const key of ctx.keys) {
|
|
1196
|
-
const str = workerBindingToString(workerEnv[key]);
|
|
1641
|
+
const str = workerBindingToString(Object.hasOwn(workerEnv, key) ? workerEnv[key] : undefined);
|
|
1197
1642
|
if (str !== undefined) {
|
|
1198
1643
|
out[key] = str;
|
|
1199
1644
|
}
|
|
1200
1645
|
}
|
|
1201
1646
|
return out;
|
|
1202
1647
|
};
|
|
1648
|
+
return Object.assign(loader, { source: "Cloudflare Worker bindings" });
|
|
1203
1649
|
}
|
|
1204
1650
|
function workerBindingToString(value) {
|
|
1205
1651
|
if (value === undefined || value === null) {
|
|
@@ -1216,266 +1662,88 @@ function workerBindingToString(value) {
|
|
|
1216
1662
|
}
|
|
1217
1663
|
return String(value);
|
|
1218
1664
|
}
|
|
1665
|
+
|
|
1666
|
+
// src/secrets/loaders.ts
|
|
1219
1667
|
function infisicalLoader(config) {
|
|
1220
|
-
|
|
1668
|
+
validateInfisicalOrganizationId(config.organizationId);
|
|
1669
|
+
const loader = async (ctx) => fetchInfisicalSecrets({
|
|
1221
1670
|
keys: ctx.keys,
|
|
1222
1671
|
projectId: config.projectId,
|
|
1672
|
+
organizationId: config.organizationId,
|
|
1223
1673
|
environment: ctx.environment,
|
|
1224
1674
|
siteUrl: config.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
|
|
1225
1675
|
clientId: config.clientId,
|
|
1226
|
-
clientSecret: config.clientSecret
|
|
1676
|
+
clientSecret: config.clientSecret,
|
|
1677
|
+
secretPath: config.secretPath,
|
|
1678
|
+
timeoutMs: config.timeoutMs ?? ctx.timeoutMs,
|
|
1679
|
+
signal: ctx.signal
|
|
1227
1680
|
});
|
|
1681
|
+
return Object.assign(loader, { source: "Infisical HTTP" });
|
|
1228
1682
|
}
|
|
1229
1683
|
async function processEnvLoader(_ctx) {
|
|
1230
1684
|
return {};
|
|
1231
1685
|
}
|
|
1232
|
-
|
|
1686
|
+
Object.assign(processEnvLoader, { source: "process.env" });
|
|
1233
1687
|
// src/secrets/vite.ts
|
|
1234
|
-
function getViteDefine(secretSet) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1688
|
+
function getViteDefine(secretSet, options = {}) {
|
|
1689
|
+
const values = secretSet.secrets();
|
|
1690
|
+
const keys = options.publicKeys ?? Object.keys(values);
|
|
1691
|
+
if (!options.publicKeys && keys.some((key) => !key.startsWith("VITE_"))) {
|
|
1692
|
+
throw new SecretLoadError("CONFIGURATION", "Vite secret sets must contain only VITE_ keys, or provide an explicit publicKeys list.");
|
|
1693
|
+
}
|
|
1694
|
+
for (const key of keys) {
|
|
1695
|
+
if (!Object.hasOwn(values, key) || !/^[A-Za-z_$][\w$]*$/.test(key)) {
|
|
1696
|
+
throw new SecretLoadError("CONFIGURATION", "Vite publicKeys must reference configured keys with valid JavaScript identifiers.");
|
|
1697
|
+
}
|
|
1698
|
+
if (["MODE", "BASE_URL", "PROD", "DEV", "SSR"].includes(key)) {
|
|
1699
|
+
throw new SecretLoadError("CONFIGURATION", "Vite built-in environment constants cannot be overridden by secrets.");
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
return Object.fromEntries(keys.map((key) => [`import.meta.env.${key}`, JSON.stringify(values[key])]));
|
|
1239
1703
|
}
|
|
1240
|
-
function viteSecretSetPlugin(secretSet) {
|
|
1704
|
+
function viteSecretSetPlugin(secretSet, options = {}) {
|
|
1241
1705
|
return {
|
|
1242
1706
|
name: "hanzio-secret-set",
|
|
1243
|
-
config: () => ({
|
|
1244
|
-
define: getViteDefine(secretSet)
|
|
1245
|
-
})
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
// src/cool-console-log/index.ts
|
|
1250
|
-
var terminalColors = {
|
|
1251
|
-
reset: "\x1B[0m",
|
|
1252
|
-
bold: "\x1B[1m",
|
|
1253
|
-
dim: "\x1B[2m",
|
|
1254
|
-
italic: "\x1B[3m",
|
|
1255
|
-
underline: "\x1B[4m",
|
|
1256
|
-
black: "\x1B[30m",
|
|
1257
|
-
red: "\x1B[31m",
|
|
1258
|
-
green: "\x1B[32m",
|
|
1259
|
-
yellow: "\x1B[33m",
|
|
1260
|
-
blue: "\x1B[34m",
|
|
1261
|
-
magenta: "\x1B[35m",
|
|
1262
|
-
cyan: "\x1B[36m",
|
|
1263
|
-
white: "\x1B[37m",
|
|
1264
|
-
gray: "\x1B[90m",
|
|
1265
|
-
brightRed: "\x1B[91m",
|
|
1266
|
-
brightGreen: "\x1B[92m",
|
|
1267
|
-
brightYellow: "\x1B[93m",
|
|
1268
|
-
brightBlue: "\x1B[94m",
|
|
1269
|
-
brightMagenta: "\x1B[95m",
|
|
1270
|
-
brightCyan: "\x1B[96m",
|
|
1271
|
-
brightWhite: "\x1B[97m",
|
|
1272
|
-
bgBlack: "\x1B[40m",
|
|
1273
|
-
bgRed: "\x1B[41m",
|
|
1274
|
-
bgGreen: "\x1B[42m",
|
|
1275
|
-
bgYellow: "\x1B[43m",
|
|
1276
|
-
bgBlue: "\x1B[44m",
|
|
1277
|
-
bgMagenta: "\x1B[45m",
|
|
1278
|
-
bgCyan: "\x1B[46m",
|
|
1279
|
-
bgWhite: "\x1B[47m"
|
|
1280
|
-
};
|
|
1281
|
-
var levelColors = {
|
|
1282
|
-
debug: { badge: terminalColors.gray, text: terminalColors.dim },
|
|
1283
|
-
info: { badge: terminalColors.cyan, text: terminalColors.reset },
|
|
1284
|
-
warn: { badge: terminalColors.yellow, text: terminalColors.yellow },
|
|
1285
|
-
error: { badge: terminalColors.red, text: terminalColors.brightRed }
|
|
1286
|
-
};
|
|
1287
|
-
function formatAttributes(attrs) {
|
|
1288
|
-
if (!attrs || Object.keys(attrs).length === 0)
|
|
1289
|
-
return "";
|
|
1290
|
-
const formatted = Object.entries(attrs).map(([k, v]) => `${terminalColors.dim}${k}=${terminalColors.reset}${v}`).join(" ");
|
|
1291
|
-
return ` ${formatted}`;
|
|
1292
|
-
}
|
|
1293
|
-
function createCoolLogger(options = {}) {
|
|
1294
|
-
const { colorsInDevOnly = true, onLog } = options;
|
|
1295
|
-
const isDev = true;
|
|
1296
|
-
const useColors = colorsInDevOnly ? isDev : true;
|
|
1297
|
-
const createLogMethod = (level) => (message, attributes) => {
|
|
1298
|
-
onLog?.(level, message, attributes);
|
|
1299
|
-
if (level === "debug" && !isDev)
|
|
1300
|
-
return;
|
|
1301
|
-
if (useColors) {
|
|
1302
|
-
const { badge, text } = levelColors[level];
|
|
1303
|
-
const label = level.toUpperCase().padEnd(5);
|
|
1304
|
-
const attrStr = formatAttributes(attributes);
|
|
1305
|
-
console.log(`${badge}[${label}]${terminalColors.reset} ${text}${message}${terminalColors.reset}${attrStr}`);
|
|
1306
|
-
} else {
|
|
1307
|
-
console[level](`[${level.toUpperCase()}]`, message, attributes ?? "");
|
|
1308
|
-
}
|
|
1309
|
-
};
|
|
1310
|
-
return {
|
|
1311
|
-
debug: createLogMethod("debug"),
|
|
1312
|
-
info: createLogMethod("info"),
|
|
1313
|
-
warn: createLogMethod("warn"),
|
|
1314
|
-
error: createLogMethod("error")
|
|
1707
|
+
config: () => ({ define: getViteDefine(secretSet, options) })
|
|
1315
1708
|
};
|
|
1316
1709
|
}
|
|
1317
|
-
function logOperationSummary(operationName, durationMs, success, options = {}) {
|
|
1318
|
-
const { devOnly = true, error } = options;
|
|
1319
|
-
const isDev = true;
|
|
1320
|
-
if (devOnly && !isDev)
|
|
1321
|
-
return;
|
|
1322
|
-
const c = terminalColors;
|
|
1323
|
-
const statusIcon = success ? `${c.brightGreen}✓${c.reset}` : `${c.brightRed}✗${c.reset}`;
|
|
1324
|
-
const statusColor = success ? c.green : c.red;
|
|
1325
|
-
const durationStr = `${c.dim}${durationMs.toFixed(1)}ms${c.reset}`;
|
|
1326
|
-
const errorMessage = error ? ` ${c.dim}·${c.reset} ${c.red}${error instanceof Error ? error.message : String(error)}${c.reset}` : "";
|
|
1327
|
-
console.log(`${statusColor}${c.bold}${operationName}${c.reset} ${statusIcon} ${c.dim}·${c.reset} ${durationStr}${errorMessage}`);
|
|
1328
|
-
}
|
|
1329
|
-
function colorize(text, color, ...modifiers) {
|
|
1330
|
-
const isDev = true;
|
|
1331
|
-
if (!isDev)
|
|
1332
|
-
return text;
|
|
1333
|
-
const colorCodes = [
|
|
1334
|
-
terminalColors[color],
|
|
1335
|
-
...modifiers.map((m) => terminalColors[m])
|
|
1336
|
-
].join("");
|
|
1337
|
-
return `${colorCodes}${text}${terminalColors.reset}`;
|
|
1338
|
-
}
|
|
1339
|
-
function logBanner(text, color = "cyan", options = {}) {
|
|
1340
|
-
const { devOnly = true } = options;
|
|
1341
|
-
const isDev = true;
|
|
1342
|
-
if (devOnly && !isDev) {
|
|
1343
|
-
console.log(`=== ${text} ===`);
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
const c = terminalColors;
|
|
1347
|
-
const padding = 2;
|
|
1348
|
-
const line = "═".repeat(text.length + padding * 2);
|
|
1349
|
-
console.log(`${c[color]}╔${line}╗${c.reset}`);
|
|
1350
|
-
console.log(`${c[color]}║${" ".repeat(padding)}${c.bold}${text}${c.reset}${c[color]}${" ".repeat(padding)}║${c.reset}`);
|
|
1351
|
-
console.log(`${c[color]}╚${line}╝${c.reset}`);
|
|
1352
|
-
}
|
|
1353
1710
|
|
|
1354
1711
|
// src/secrets/index.ts
|
|
1355
|
-
var logger = createCoolLogger();
|
|
1356
1712
|
function getSecretEnvironment() {
|
|
1357
|
-
|
|
1358
|
-
if (env === "production" || env === "prod")
|
|
1359
|
-
return "prod";
|
|
1360
|
-
if (env === "staging")
|
|
1361
|
-
return "staging";
|
|
1362
|
-
return "dev";
|
|
1713
|
+
return resolveSecretEnvironment(undefined, getProcessEnvironment());
|
|
1363
1714
|
}
|
|
1364
|
-
async function
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
...resolvedOptions
|
|
1374
|
-
});
|
|
1375
|
-
cachedSecrets = nextSecrets;
|
|
1376
|
-
return nextSecrets;
|
|
1377
|
-
};
|
|
1378
|
-
const secretSet = createSecretSet({
|
|
1379
|
-
getCachedSecrets: () => cachedSecrets,
|
|
1380
|
-
reload: loadSecrets
|
|
1381
|
-
});
|
|
1382
|
-
await loadSecrets();
|
|
1383
|
-
return secretSet;
|
|
1715
|
+
async function defineSecretSet2(keys, options) {
|
|
1716
|
+
let loader;
|
|
1717
|
+
if (!options.loader) {
|
|
1718
|
+
validateInfisicalOrganizationId(options.organizationId);
|
|
1719
|
+
loader = createDefaultInfisicalLoader(options);
|
|
1720
|
+
} else {
|
|
1721
|
+
loader = options.loader;
|
|
1722
|
+
}
|
|
1723
|
+
return defineSecretSet(keys, { ...options, loader }, getProcessEnvironment());
|
|
1384
1724
|
}
|
|
1385
|
-
function
|
|
1386
|
-
|
|
1387
|
-
const loader = options.loader ? options.loader : (context) => loadInfisicalSecrets({
|
|
1725
|
+
function createDefaultInfisicalLoader(options) {
|
|
1726
|
+
return (context) => loadInfisicalSecrets({
|
|
1388
1727
|
...context,
|
|
1389
1728
|
projectId: options.projectId,
|
|
1729
|
+
organizationId: options.organizationId,
|
|
1390
1730
|
siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
|
|
1731
|
+
secretPath: options.secretPath,
|
|
1732
|
+
auth: options.auth,
|
|
1391
1733
|
clientIdEnvKey: options.clientIdEnvKey ?? "INFISICAL_CLIENT_ID",
|
|
1392
1734
|
clientSecretEnvKey: options.clientSecretEnvKey ?? "INFISICAL_CLIENT_SECRET"
|
|
1393
1735
|
});
|
|
1394
|
-
return {
|
|
1395
|
-
loader,
|
|
1396
|
-
environment
|
|
1397
|
-
};
|
|
1398
|
-
}
|
|
1399
|
-
async function loadAndMergeSecrets({
|
|
1400
|
-
keySet,
|
|
1401
|
-
cachedSecrets,
|
|
1402
|
-
loader,
|
|
1403
|
-
environment,
|
|
1404
|
-
force
|
|
1405
|
-
}) {
|
|
1406
|
-
const processEnvSecrets = readProcessEnvSecrets(keySet, force ? cachedSecrets : null);
|
|
1407
|
-
const keysMissingFromEnv = keySet.filter((key) => !processEnvSecrets[key]);
|
|
1408
|
-
logSecretEnvironment(environment, keySet.length);
|
|
1409
|
-
const loaderKeys = force ? keySet : keysMissingFromEnv;
|
|
1410
|
-
const loadedSecrets = loaderKeys.length === 0 ? {} : await loader({
|
|
1411
|
-
keys: loaderKeys,
|
|
1412
|
-
environment
|
|
1413
|
-
});
|
|
1414
|
-
const missingKeys = keySet.filter((key) => !(processEnvSecrets[key] ?? loadedSecrets[key]));
|
|
1415
|
-
if (missingKeys.length > 0) {
|
|
1416
|
-
throw new Error(`Missing secrets: ${missingKeys.join(", ")}`);
|
|
1417
|
-
}
|
|
1418
|
-
const nextSecrets = Object.fromEntries(keySet.map((key) => [
|
|
1419
|
-
key,
|
|
1420
|
-
processEnvSecrets[key] ?? loadedSecrets[key]
|
|
1421
|
-
]));
|
|
1422
|
-
for (const [key, value] of Object.entries(nextSecrets)) {
|
|
1423
|
-
process.env[key] = value;
|
|
1424
|
-
}
|
|
1425
|
-
return nextSecrets;
|
|
1426
|
-
}
|
|
1427
|
-
function createSecretSet({
|
|
1428
|
-
getCachedSecrets,
|
|
1429
|
-
reload
|
|
1430
|
-
}) {
|
|
1431
|
-
const secretSet = {
|
|
1432
|
-
secret(key) {
|
|
1433
|
-
const value = getCachedSecrets()?.[key] ?? process.env[key];
|
|
1434
|
-
if (!value) {
|
|
1435
|
-
throw new Error(`Secret ${key} not found`);
|
|
1436
|
-
}
|
|
1437
|
-
return value;
|
|
1438
|
-
},
|
|
1439
|
-
secrets() {
|
|
1440
|
-
const cachedSecrets = getCachedSecrets();
|
|
1441
|
-
if (!cachedSecrets) {
|
|
1442
|
-
throw new Error("Secrets not initialized.");
|
|
1443
|
-
}
|
|
1444
|
-
return { ...cachedSecrets };
|
|
1445
|
-
},
|
|
1446
|
-
async reload() {
|
|
1447
|
-
await reload(true);
|
|
1448
|
-
return secretSet;
|
|
1449
|
-
}
|
|
1450
|
-
};
|
|
1451
|
-
return secretSet;
|
|
1452
|
-
}
|
|
1453
|
-
function resolveEnvironment(environment) {
|
|
1454
|
-
if (typeof environment === "function")
|
|
1455
|
-
return environment();
|
|
1456
|
-
return environment ?? getSecretEnvironment();
|
|
1457
|
-
}
|
|
1458
|
-
function logSecretEnvironment(environment, secretCount) {
|
|
1459
|
-
logger.info(colorize("Loading secrets", "cyan", "bold"), {
|
|
1460
|
-
environment: colorize(environment, "brightCyan", "bold"),
|
|
1461
|
-
count: secretCount
|
|
1462
|
-
});
|
|
1463
|
-
}
|
|
1464
|
-
function readProcessEnvSecrets(keys, previousSecrets = null) {
|
|
1465
|
-
return Object.fromEntries(keys.filter((key) => {
|
|
1466
|
-
const value = process.env[key];
|
|
1467
|
-
return value && value !== previousSecrets?.[key];
|
|
1468
|
-
}).map((key) => [key, process.env[key]]));
|
|
1469
1736
|
}
|
|
1470
1737
|
export {
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
loadInfisicalSecrets,
|
|
1474
|
-
infisicalLoader,
|
|
1475
|
-
getViteDefine,
|
|
1476
|
-
getSecretEnvironment,
|
|
1477
|
-
fetchInfisicalSecrets,
|
|
1478
|
-
defineSecretSet,
|
|
1738
|
+
DEFAULT_INFISICAL_SITE_URL,
|
|
1739
|
+
SecretLoadError,
|
|
1479
1740
|
cloudflareWorkerEnvLoader,
|
|
1480
|
-
|
|
1741
|
+
defineSecretSet2 as defineSecretSet,
|
|
1742
|
+
fetchInfisicalSecrets,
|
|
1743
|
+
getSecretEnvironment,
|
|
1744
|
+
getViteDefine,
|
|
1745
|
+
infisicalLoader,
|
|
1746
|
+
loadInfisicalSecrets,
|
|
1747
|
+
processEnvLoader,
|
|
1748
|
+
viteSecretSetPlugin
|
|
1481
1749
|
};
|