@secrefs/node 0.1.0 → 0.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 +1 -1
- package/dist/index.cjs +331 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -1
- package/dist/index.d.ts +178 -1
- package/dist/index.js +326 -10
- package/dist/index.js.map +1 -1
- package/dist/secrefs.cjs +361 -18
- package/dist/secrefs.cjs.map +1 -1
- package/package.json +5 -4
package/dist/secrefs.cjs
CHANGED
|
@@ -24,24 +24,151 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// bin/secrefs.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_node_fs2 = require("fs");
|
|
28
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
29
29
|
var import_commander = require("commander");
|
|
30
30
|
var import_cross_spawn = __toESM(require("cross-spawn"), 1);
|
|
31
31
|
|
|
32
32
|
// src/providers/aws.ts
|
|
33
33
|
var import_client_secrets_manager = require("@aws-sdk/client-secrets-manager");
|
|
34
34
|
|
|
35
|
+
// src/providers/errors.ts
|
|
36
|
+
var AUTH_NAMES = /* @__PURE__ */ new Set([
|
|
37
|
+
"CredentialsProviderError",
|
|
38
|
+
"TokenProviderError",
|
|
39
|
+
"ExpiredToken",
|
|
40
|
+
"ExpiredTokenException",
|
|
41
|
+
"InvalidClientTokenId",
|
|
42
|
+
"UnrecognizedClientException",
|
|
43
|
+
"InvalidIdentityToken",
|
|
44
|
+
"AuthFailure",
|
|
45
|
+
"SSOTokenProviderFailure"
|
|
46
|
+
]);
|
|
47
|
+
var NOT_FOUND_NAMES = /* @__PURE__ */ new Set([
|
|
48
|
+
"ResourceNotFoundException",
|
|
49
|
+
"NoSuchEntity",
|
|
50
|
+
"SecretNotFound"
|
|
51
|
+
]);
|
|
52
|
+
var DENIED_NAMES = /* @__PURE__ */ new Set([
|
|
53
|
+
"AccessDeniedException",
|
|
54
|
+
"AccessDenied",
|
|
55
|
+
"AuthorizationError",
|
|
56
|
+
"UnauthorizedOperation"
|
|
57
|
+
]);
|
|
58
|
+
var TRANSIENT_NAMES = /* @__PURE__ */ new Set([
|
|
59
|
+
"TimeoutError",
|
|
60
|
+
"NetworkingError",
|
|
61
|
+
"RequestTimeout",
|
|
62
|
+
"RequestTimeoutException",
|
|
63
|
+
"ThrottlingException",
|
|
64
|
+
"TooManyRequestsException",
|
|
65
|
+
"InternalServiceError",
|
|
66
|
+
"InternalServerError",
|
|
67
|
+
"ServiceUnavailable",
|
|
68
|
+
"ServiceUnavailableException",
|
|
69
|
+
"AbortError",
|
|
70
|
+
"ECONNRESET",
|
|
71
|
+
"ECONNREFUSED",
|
|
72
|
+
"ETIMEDOUT",
|
|
73
|
+
"EAI_AGAIN"
|
|
74
|
+
]);
|
|
75
|
+
var AUTH_FRAGMENTS = [
|
|
76
|
+
"could not load credentials",
|
|
77
|
+
"sso session associated with this profile has expired",
|
|
78
|
+
"security token included in the request is expired",
|
|
79
|
+
"unable to locate credentials",
|
|
80
|
+
"token is expired",
|
|
81
|
+
"credentials have expired",
|
|
82
|
+
"is expired"
|
|
83
|
+
];
|
|
84
|
+
var TRANSIENT_FRAGMENTS = [
|
|
85
|
+
"socket hang up",
|
|
86
|
+
"network error",
|
|
87
|
+
"timed out",
|
|
88
|
+
"timeout",
|
|
89
|
+
"econnreset",
|
|
90
|
+
"econnrefused",
|
|
91
|
+
"getaddrinfo"
|
|
92
|
+
];
|
|
93
|
+
function nameOf(err) {
|
|
94
|
+
if (typeof err !== "object" || err === null) return "";
|
|
95
|
+
const e = err;
|
|
96
|
+
for (const candidate of [e.name, e.code, e.__type]) {
|
|
97
|
+
if (typeof candidate === "string" && candidate) return candidate;
|
|
98
|
+
}
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
function statusOf(err) {
|
|
102
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
103
|
+
const meta = err.$metadata;
|
|
104
|
+
if (typeof meta?.httpStatusCode === "number") return meta.httpStatusCode;
|
|
105
|
+
const status = err.status ?? err.statusCode;
|
|
106
|
+
return typeof status === "number" ? status : void 0;
|
|
107
|
+
}
|
|
108
|
+
function classifyError(err) {
|
|
109
|
+
const name = nameOf(err);
|
|
110
|
+
if (AUTH_NAMES.has(name)) return "auth";
|
|
111
|
+
if (NOT_FOUND_NAMES.has(name)) return "not_found";
|
|
112
|
+
if (DENIED_NAMES.has(name)) return "denied";
|
|
113
|
+
if (TRANSIENT_NAMES.has(name)) return "transient";
|
|
114
|
+
const status = statusOf(err);
|
|
115
|
+
if (status === 401) return "auth";
|
|
116
|
+
if (status === 403) return "denied";
|
|
117
|
+
if (status === 404) return "not_found";
|
|
118
|
+
if (status === 408 || status === 429) return "transient";
|
|
119
|
+
if (status !== void 0 && status >= 500) return "transient";
|
|
120
|
+
const message = (err instanceof Error ? err.message : String(err ?? "")).toLowerCase();
|
|
121
|
+
if (AUTH_FRAGMENTS.some((f) => message.includes(f))) return "auth";
|
|
122
|
+
if (TRANSIENT_FRAGMENTS.some((f) => message.includes(f))) return "transient";
|
|
123
|
+
return "unknown";
|
|
124
|
+
}
|
|
125
|
+
function isStaleServable(kind) {
|
|
126
|
+
return kind === "transient";
|
|
127
|
+
}
|
|
128
|
+
function remedyFor(kind, provider, err) {
|
|
129
|
+
if (kind !== "auth") return void 0;
|
|
130
|
+
const message = err instanceof Error ? err.message : "";
|
|
131
|
+
if (/sso session/i.test(message)) {
|
|
132
|
+
const profile = process.env.AWS_PROFILE;
|
|
133
|
+
return profile ? `Run: aws sso login --profile ${profile}` : "Run: aws sso login --profile <your-profile>";
|
|
134
|
+
}
|
|
135
|
+
switch (provider) {
|
|
136
|
+
case "aws": {
|
|
137
|
+
const profile = process.env.AWS_PROFILE;
|
|
138
|
+
return profile ? `Check credentials for AWS profile "${profile}" - if it uses SSO, run: aws sso login --profile ${profile}` : "No AWS credentials found. Set AWS_PROFILE, export static keys, or attach an instance role.";
|
|
139
|
+
}
|
|
140
|
+
case "bitwarden":
|
|
141
|
+
return "Set BWS_ACCESS_TOKEN to a valid Bitwarden machine account token.";
|
|
142
|
+
case "vault":
|
|
143
|
+
return "Set VAULT_ADDR and VAULT_TOKEN, or renew the token if it has expired.";
|
|
144
|
+
default:
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
35
149
|
// src/providers/base.ts
|
|
36
150
|
var SecretFetchError = class extends Error {
|
|
37
151
|
constructor(provider, path3, cause) {
|
|
38
|
-
|
|
152
|
+
const kind = classifyError(cause);
|
|
153
|
+
super(
|
|
154
|
+
kind === "auth" ? `[${provider}] cannot authenticate: ${errorMessage(cause)}` : `[${provider}] failed to fetch secret at "${path3}": ${errorMessage(cause)}`
|
|
155
|
+
);
|
|
39
156
|
this.provider = provider;
|
|
40
157
|
this.path = path3;
|
|
158
|
+
this.cause = cause;
|
|
41
159
|
this.name = "SecretFetchError";
|
|
160
|
+
this.kind = kind;
|
|
161
|
+
this.remedy = remedyFor(kind, provider, cause);
|
|
42
162
|
}
|
|
43
163
|
provider;
|
|
44
164
|
path;
|
|
165
|
+
cause;
|
|
166
|
+
/** Whose problem this is - see {@link SecretErrorKind}. Classified from
|
|
167
|
+
* `cause` so every existing throw site is categorised without having to
|
|
168
|
+
* know about categories. */
|
|
169
|
+
kind;
|
|
170
|
+
/** The action that fixes it, when there is one (auth failures). */
|
|
171
|
+
remedy;
|
|
45
172
|
};
|
|
46
173
|
var BaseSecretProvider = class {
|
|
47
174
|
async fetchBatch(requests) {
|
|
@@ -86,6 +213,9 @@ function extractField(raw, field, context) {
|
|
|
86
213
|
return typeof current === "object" ? JSON.stringify(current) : String(current);
|
|
87
214
|
}
|
|
88
215
|
|
|
216
|
+
// src/providers/aws.ts
|
|
217
|
+
var import_credential_providers = require("@aws-sdk/credential-providers");
|
|
218
|
+
|
|
89
219
|
// src/ttlCache.ts
|
|
90
220
|
var TtlCache = class {
|
|
91
221
|
/** Settled values, only populated when a TTL is configured. */
|
|
@@ -96,9 +226,15 @@ var TtlCache = class {
|
|
|
96
226
|
* stays correct even with caching fully disabled. */
|
|
97
227
|
inFlight = /* @__PURE__ */ new Map();
|
|
98
228
|
ttlMs;
|
|
229
|
+
staleGraceMs;
|
|
230
|
+
isStaleServable;
|
|
231
|
+
onStale;
|
|
99
232
|
now;
|
|
100
233
|
constructor(options = {}) {
|
|
101
234
|
this.ttlMs = options.ttlMs ?? 0;
|
|
235
|
+
this.staleGraceMs = options.staleGraceMs ?? 0;
|
|
236
|
+
this.isStaleServable = options.isStaleServable ?? (() => false);
|
|
237
|
+
this.onStale = options.onStale;
|
|
102
238
|
this.now = options.now ?? Date.now;
|
|
103
239
|
}
|
|
104
240
|
/**
|
|
@@ -122,11 +258,19 @@ var TtlCache = class {
|
|
|
122
258
|
this.inFlight.set(key, pending);
|
|
123
259
|
try {
|
|
124
260
|
const value = await pending;
|
|
125
|
-
if (this.ttlMs > 0) {
|
|
261
|
+
if (this.ttlMs > 0 || this.staleGraceMs > 0) {
|
|
126
262
|
this.entries.set(key, { value: Promise.resolve(value), storedAt: this.now() });
|
|
127
263
|
}
|
|
128
264
|
return value;
|
|
129
265
|
} catch (err) {
|
|
266
|
+
const previous = this.entries.get(key);
|
|
267
|
+
if (previous && this.staleGraceMs > 0 && this.isStaleServable(err)) {
|
|
268
|
+
const ageMs = this.now() - previous.storedAt;
|
|
269
|
+
if (ageMs <= this.staleGraceMs) {
|
|
270
|
+
this.onStale?.(key, ageMs, err);
|
|
271
|
+
return previous.value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
130
274
|
this.entries.delete(key);
|
|
131
275
|
throw err;
|
|
132
276
|
} finally {
|
|
@@ -190,6 +334,7 @@ var AwsSecretsManagerProvider = class extends BaseSecretProvider {
|
|
|
190
334
|
name = "aws";
|
|
191
335
|
explicitClient;
|
|
192
336
|
region;
|
|
337
|
+
profile;
|
|
193
338
|
controlPlane;
|
|
194
339
|
controlPlaneClient;
|
|
195
340
|
ambientClient = null;
|
|
@@ -198,8 +343,14 @@ var AwsSecretsManagerProvider = class extends BaseSecretProvider {
|
|
|
198
343
|
super();
|
|
199
344
|
this.explicitClient = options.client;
|
|
200
345
|
this.region = options.region;
|
|
346
|
+
this.profile = options.profile;
|
|
201
347
|
this.controlPlane = options.controlPlane;
|
|
202
|
-
this.rawCache = new TtlCache({
|
|
348
|
+
this.rawCache = new TtlCache({
|
|
349
|
+
ttlMs: options.cacheTtlMs,
|
|
350
|
+
staleGraceMs: options.staleGraceMs,
|
|
351
|
+
isStaleServable: (err) => isStaleServable(classifyError(err)),
|
|
352
|
+
onStale: options.onStaleValue
|
|
353
|
+
});
|
|
203
354
|
if (this.controlPlane) {
|
|
204
355
|
this.controlPlaneClient = this.controlPlane.client ?? new ControlPlaneClient({ baseUrl: this.controlPlane.baseUrl, token: this.controlPlane.token });
|
|
205
356
|
}
|
|
@@ -218,7 +369,15 @@ var AwsSecretsManagerProvider = class extends BaseSecretProvider {
|
|
|
218
369
|
}
|
|
219
370
|
return this.buildClientFromMintedCredentials(minted.credentials);
|
|
220
371
|
}
|
|
221
|
-
if (!this.ambientClient)
|
|
372
|
+
if (!this.ambientClient) {
|
|
373
|
+
this.ambientClient = new import_client_secrets_manager.SecretsManagerClient({
|
|
374
|
+
region: this.region,
|
|
375
|
+
// fromNodeProviderChain honours the same precedence as the
|
|
376
|
+
// ambient default, just pinned to one profile - so instance
|
|
377
|
+
// roles and env vars still work when no profile is named.
|
|
378
|
+
...this.profile ? { credentials: (0, import_credential_providers.fromNodeProviderChain)({ profile: this.profile }) } : {}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
222
381
|
return this.ambientClient;
|
|
223
382
|
}
|
|
224
383
|
buildClientFromMintedCredentials(credentials) {
|
|
@@ -244,7 +403,8 @@ var AwsSecretsManagerProvider = class extends BaseSecretProvider {
|
|
|
244
403
|
}
|
|
245
404
|
throw new Error(`secret "${path3}" has no SecretString or SecretBinary payload`);
|
|
246
405
|
} catch (err) {
|
|
247
|
-
|
|
406
|
+
if (err instanceof SecretFetchError) throw err;
|
|
407
|
+
throw new SecretFetchError(this.name, path3, err);
|
|
248
408
|
}
|
|
249
409
|
});
|
|
250
410
|
}
|
|
@@ -579,16 +739,37 @@ function parseSecretRef(raw) {
|
|
|
579
739
|
}
|
|
580
740
|
|
|
581
741
|
// src/resolver.ts
|
|
742
|
+
function formatFailures(errors) {
|
|
743
|
+
const auth = errors.filter((e) => e.kind === "auth");
|
|
744
|
+
const rest = errors.filter((e) => e.kind !== "auth");
|
|
745
|
+
const lines = [];
|
|
746
|
+
for (const provider of [...new Set(auth.map((e) => e.provider ?? "unknown"))]) {
|
|
747
|
+
const group = auth.filter((e) => (e.provider ?? "unknown") === provider);
|
|
748
|
+
lines.push(`Cannot authenticate to provider "${provider}".`);
|
|
749
|
+
lines.push(` ${group[0].message}`);
|
|
750
|
+
const remedy = group.find((e) => e.remedy)?.remedy;
|
|
751
|
+
if (remedy) lines.push(` ${remedy}`);
|
|
752
|
+
lines.push(` Not resolved: ${group.map((e) => e.key).join(", ")}`);
|
|
753
|
+
}
|
|
754
|
+
if (rest.length > 0) {
|
|
755
|
+
if (lines.length > 0) lines.push("");
|
|
756
|
+
lines.push(`Failed to resolve ${rest.length} secret reference(s):`);
|
|
757
|
+
for (const e of rest) lines.push(` - ${e.key}: ${e.ref} -> ${e.message}`);
|
|
758
|
+
}
|
|
759
|
+
return lines.join("\n");
|
|
760
|
+
}
|
|
582
761
|
var SecRefsResolutionError = class extends Error {
|
|
583
762
|
constructor(errors) {
|
|
584
|
-
super(
|
|
585
|
-
`Failed to resolve ${errors.length} secret reference(s):
|
|
586
|
-
` + errors.map((e) => ` - ${e.key}: ${e.ref} -> ${e.message}`).join("\n")
|
|
587
|
-
);
|
|
763
|
+
super(formatFailures(errors));
|
|
588
764
|
this.errors = errors;
|
|
589
765
|
this.name = "SecRefsResolutionError";
|
|
590
766
|
}
|
|
591
767
|
errors;
|
|
768
|
+
/** True when every failure was an environment/auth problem, so a caller
|
|
769
|
+
* can tell "your credentials lapsed" from "your references are wrong". */
|
|
770
|
+
get isAuthOnly() {
|
|
771
|
+
return this.errors.length > 0 && this.errors.every((e) => e.kind === "auth");
|
|
772
|
+
}
|
|
592
773
|
};
|
|
593
774
|
async function resolveOne(ref, providers) {
|
|
594
775
|
const provider = providers[ref.provider];
|
|
@@ -628,7 +809,15 @@ async function expandKeyValueMap(input, options) {
|
|
|
628
809
|
if (result.status === "fulfilled") {
|
|
629
810
|
output[key] = result.value;
|
|
630
811
|
} else {
|
|
631
|
-
|
|
812
|
+
const reason = result.reason;
|
|
813
|
+
errors.push({
|
|
814
|
+
key,
|
|
815
|
+
ref: ref.raw,
|
|
816
|
+
message: errorMessage(reason),
|
|
817
|
+
kind: reason instanceof SecretFetchError ? reason.kind : void 0,
|
|
818
|
+
provider: reason instanceof SecretFetchError ? reason.provider : ref.provider,
|
|
819
|
+
remedy: reason instanceof SecretFetchError ? reason.remedy : void 0
|
|
820
|
+
});
|
|
632
821
|
}
|
|
633
822
|
});
|
|
634
823
|
if (errors.length > 0) {
|
|
@@ -693,6 +882,128 @@ function parseEnvFileText(rawText) {
|
|
|
693
882
|
return recoverTruncatedSecRefs(rawText, parsed);
|
|
694
883
|
}
|
|
695
884
|
|
|
885
|
+
// src/config.ts
|
|
886
|
+
var import_node_fs = require("fs");
|
|
887
|
+
var import_node_path2 = require("path");
|
|
888
|
+
var CONFIG_FILENAME = "secrefs.config.json";
|
|
889
|
+
var ConfigError = class extends Error {
|
|
890
|
+
constructor(message) {
|
|
891
|
+
super(message);
|
|
892
|
+
this.name = "ConfigError";
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
896
|
+
"token",
|
|
897
|
+
"accessToken",
|
|
898
|
+
"access_token",
|
|
899
|
+
"secret",
|
|
900
|
+
"secretKey",
|
|
901
|
+
"secretAccessKey",
|
|
902
|
+
"password",
|
|
903
|
+
"apiKey",
|
|
904
|
+
"credential",
|
|
905
|
+
"credentials"
|
|
906
|
+
]);
|
|
907
|
+
function assertNoInlineSecrets(alias, config) {
|
|
908
|
+
for (const key of Object.keys(config)) {
|
|
909
|
+
if (FORBIDDEN_KEYS.has(key)) {
|
|
910
|
+
throw new ConfigError(
|
|
911
|
+
`${CONFIG_FILENAME}: provider "${alias}" sets "${key}". This file is meant to be committed and must never contain a credential. Reference the environment variable that holds it instead - e.g. "tokenEnv": "BWS_ACCESS_TOKEN".`
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function requireEnv(alias, varName, env) {
|
|
917
|
+
const value = env[varName];
|
|
918
|
+
if (!value) {
|
|
919
|
+
throw new ConfigError(
|
|
920
|
+
`${CONFIG_FILENAME}: provider "${alias}" expects the credential in ${varName}, but that environment variable is not set.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
return value;
|
|
924
|
+
}
|
|
925
|
+
function parseConfig(raw, source = CONFIG_FILENAME) {
|
|
926
|
+
let parsed;
|
|
927
|
+
try {
|
|
928
|
+
parsed = JSON.parse(raw);
|
|
929
|
+
} catch (err) {
|
|
930
|
+
throw new ConfigError(`${source} is not valid JSON: ${err.message}`);
|
|
931
|
+
}
|
|
932
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
933
|
+
throw new ConfigError(`${source} must contain a JSON object.`);
|
|
934
|
+
}
|
|
935
|
+
const providers = parsed.providers;
|
|
936
|
+
if (typeof providers !== "object" || providers === null) {
|
|
937
|
+
throw new ConfigError(`${source} must have a "providers" object.`);
|
|
938
|
+
}
|
|
939
|
+
for (const [alias, value] of Object.entries(providers)) {
|
|
940
|
+
if (typeof value !== "object" || value === null) {
|
|
941
|
+
throw new ConfigError(`${source}: provider "${alias}" must be an object.`);
|
|
942
|
+
}
|
|
943
|
+
const type = value.type;
|
|
944
|
+
if (type !== "aws" && type !== "bitwarden" && type !== "vault" && type !== "local") {
|
|
945
|
+
throw new ConfigError(
|
|
946
|
+
`${source}: provider "${alias}" has type ${JSON.stringify(type)}; expected one of "aws", "bitwarden", "vault", "local".`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
assertNoInlineSecrets(alias, value);
|
|
950
|
+
}
|
|
951
|
+
return parsed;
|
|
952
|
+
}
|
|
953
|
+
function buildProviders(config, options = {}) {
|
|
954
|
+
const env = options.env ?? process.env;
|
|
955
|
+
const configDir = options.configDir ?? process.cwd();
|
|
956
|
+
const registry = {};
|
|
957
|
+
for (const [alias, entry] of Object.entries(config.providers)) {
|
|
958
|
+
switch (entry.type) {
|
|
959
|
+
case "aws":
|
|
960
|
+
registry[alias] = new AwsSecretsManagerProvider({
|
|
961
|
+
region: entry.region,
|
|
962
|
+
cacheTtlMs: entry.cacheTtlMs,
|
|
963
|
+
staleGraceMs: entry.staleGraceMs,
|
|
964
|
+
profile: entry.profile
|
|
965
|
+
});
|
|
966
|
+
break;
|
|
967
|
+
case "bitwarden":
|
|
968
|
+
registry[alias] = new BitwardenProvider({
|
|
969
|
+
accessToken: requireEnv(alias, entry.tokenEnv ?? "BWS_ACCESS_TOKEN", env),
|
|
970
|
+
organizationId: entry.organizationIdEnv ? requireEnv(alias, entry.organizationIdEnv, env) : env.BWS_ORGANIZATION_ID,
|
|
971
|
+
apiUrl: entry.apiUrl,
|
|
972
|
+
identityUrl: entry.identityUrl
|
|
973
|
+
});
|
|
974
|
+
break;
|
|
975
|
+
case "vault":
|
|
976
|
+
registry[alias] = new VaultProvider({
|
|
977
|
+
endpoint: entry.addr ?? env.VAULT_ADDR,
|
|
978
|
+
token: requireEnv(alias, entry.tokenEnv ?? "VAULT_TOKEN", env)
|
|
979
|
+
});
|
|
980
|
+
break;
|
|
981
|
+
case "local":
|
|
982
|
+
registry[alias] = new LocalProvider({
|
|
983
|
+
filePath: entry.file ? (0, import_node_path2.resolve)(configDir, entry.file) : void 0
|
|
984
|
+
});
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return registry;
|
|
989
|
+
}
|
|
990
|
+
function loadConfigFrom(dir = process.cwd()) {
|
|
991
|
+
let current = (0, import_node_path2.resolve)(dir);
|
|
992
|
+
for (; ; ) {
|
|
993
|
+
const candidate = (0, import_node_path2.resolve)(current, CONFIG_FILENAME);
|
|
994
|
+
let raw;
|
|
995
|
+
try {
|
|
996
|
+
raw = (0, import_node_fs.readFileSync)(candidate, "utf8");
|
|
997
|
+
} catch {
|
|
998
|
+
const parent = (0, import_node_path2.dirname)(current);
|
|
999
|
+
if (parent === current) return void 0;
|
|
1000
|
+
current = parent;
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
return { config: parseConfig(raw, candidate), path: candidate };
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
696
1007
|
// src/index.ts
|
|
697
1008
|
function createDefaultProviders() {
|
|
698
1009
|
return {
|
|
@@ -746,11 +1057,11 @@ var secRefs = new SecRefs();
|
|
|
746
1057
|
var CLI_VERSION = "0.1.0";
|
|
747
1058
|
function loadEnvFile(opts) {
|
|
748
1059
|
if (opts.envFile === false) return;
|
|
749
|
-
const envFilePath =
|
|
750
|
-
if (!(0,
|
|
1060
|
+
const envFilePath = import_node_path3.default.resolve(process.cwd(), opts.envFile);
|
|
1061
|
+
if (!(0, import_node_fs2.existsSync)(envFilePath)) return;
|
|
751
1062
|
let rawText;
|
|
752
1063
|
try {
|
|
753
|
-
rawText = (0,
|
|
1064
|
+
rawText = (0, import_node_fs2.readFileSync)(envFilePath, "utf8");
|
|
754
1065
|
} catch (err) {
|
|
755
1066
|
console.error(
|
|
756
1067
|
`secrefs: failed to read ${envFilePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -774,6 +1085,34 @@ function loadEnvFile(opts) {
|
|
|
774
1085
|
}
|
|
775
1086
|
}
|
|
776
1087
|
}
|
|
1088
|
+
function buildSecRefs() {
|
|
1089
|
+
let found;
|
|
1090
|
+
try {
|
|
1091
|
+
found = loadConfigFrom();
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
if (err instanceof ConfigError) {
|
|
1094
|
+
console.error(`secrefs: ${err.message}`);
|
|
1095
|
+
process.exit(1);
|
|
1096
|
+
}
|
|
1097
|
+
throw err;
|
|
1098
|
+
}
|
|
1099
|
+
if (!found) return new SecRefs();
|
|
1100
|
+
let providers;
|
|
1101
|
+
try {
|
|
1102
|
+
providers = buildProviders(found.config, { configDir: import_node_path3.default.dirname(found.path) });
|
|
1103
|
+
} catch (err) {
|
|
1104
|
+
if (err instanceof ConfigError) {
|
|
1105
|
+
console.error(`secrefs: ${err.message}`);
|
|
1106
|
+
process.exit(1);
|
|
1107
|
+
}
|
|
1108
|
+
throw err;
|
|
1109
|
+
}
|
|
1110
|
+
const aliases = Object.keys(providers);
|
|
1111
|
+
console.error(
|
|
1112
|
+
`secrefs: using ${import_node_path3.default.relative(process.cwd(), found.path) || found.path} (${aliases.length} alias${aliases.length === 1 ? "" : "es"}: ${aliases.join(", ")})`
|
|
1113
|
+
);
|
|
1114
|
+
return new SecRefs({ providers });
|
|
1115
|
+
}
|
|
777
1116
|
async function runCommand(commandArgs, opts) {
|
|
778
1117
|
if (commandArgs.length === 0) {
|
|
779
1118
|
console.error("secrefs run: no command given. Usage: secrefs run -- <command> [args...]");
|
|
@@ -785,7 +1124,7 @@ async function runCommand(commandArgs, opts) {
|
|
|
785
1124
|
} catch {
|
|
786
1125
|
return;
|
|
787
1126
|
}
|
|
788
|
-
const instance =
|
|
1127
|
+
const instance = buildSecRefs();
|
|
789
1128
|
try {
|
|
790
1129
|
const changedKeys = await instance.init();
|
|
791
1130
|
if (changedKeys.length > 0) {
|
|
@@ -795,7 +1134,11 @@ async function runCommand(commandArgs, opts) {
|
|
|
795
1134
|
}
|
|
796
1135
|
} catch (err) {
|
|
797
1136
|
if (err instanceof SecRefsResolutionError) {
|
|
798
|
-
|
|
1137
|
+
if (!err.isAuthOnly) {
|
|
1138
|
+
console.error("secrefs: failed to resolve one or more secret references:");
|
|
1139
|
+
} else {
|
|
1140
|
+
console.error("secrefs: could not authenticate to a secret provider.");
|
|
1141
|
+
}
|
|
799
1142
|
console.error(err.message);
|
|
800
1143
|
} else {
|
|
801
1144
|
console.error(`secrefs: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -839,7 +1182,7 @@ async function checkCommand(opts) {
|
|
|
839
1182
|
} catch {
|
|
840
1183
|
return;
|
|
841
1184
|
}
|
|
842
|
-
const instance =
|
|
1185
|
+
const instance = buildSecRefs();
|
|
843
1186
|
const results = await instance.check();
|
|
844
1187
|
if (results.length === 0) {
|
|
845
1188
|
console.log("secrefs check: no sec:// references found in the environment.");
|