@karmaniverous/aws-secrets-manager-tools 0.1.1 → 0.2.1
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 +102 -102
- package/dist/cli/aws-secrets-manager-tools/index.js +32 -185
- package/dist/index.d.ts +25 -56
- package/dist/mjs/index.js +32 -184
- package/package.json +15 -18
package/README.md
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
# AWS Secrets Manager Tools
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@karmaniverous/aws-secrets-manager-tools)  [](https://docs.karmanivero.us/aws-secrets-manager-tools) [](./CHANGELOG.md) [](./LICENSE)
|
|
4
|
-
|
|
5
|
-
Tools and a get-dotenv plugin for working with AWS Secrets Manager “env-map” secrets (JSON object maps of environment variables).
|
|
6
|
-
|
|
7
|
-
This package provides:
|
|
8
|
-
|
|
9
|
-
- A tools-style wrapper that owns AWS client setup (including optional AWS X-Ray capture):
|
|
10
|
-
- `AwsSecretsManagerTools`
|
|
11
|
-
- A get-dotenv plugin intended to be mounted under `aws`:
|
|
12
|
-
- `secretsPlugin()` → `aws secrets pull|push|delete`
|
|
13
|
-
- A CLI embedding get-dotenv with the secrets plugin:
|
|
14
|
-
- `aws-secrets-manager-tools`
|
|
15
|
-
|
|
16
|
-
## Documentation
|
|
17
|
-
|
|
18
|
-
- Learn the programmatic API: [AwsSecretsManagerTools guide](guides/aws-secrets-manager-tools.md)
|
|
19
|
-
- Learn the CLI and plugin behavior: [aws secrets plugin guide](guides/secrets-plugin.md)
|
|
20
|
-
- Browse the generated API reference: [TypeDoc site](https://docs.karmanivero.us/aws-secrets-manager-tools)
|
|
21
|
-
|
|
22
|
-
## Install
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
npm i @karmaniverous/aws-secrets-manager-tools
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
This package is ESM-only (Node >= 20).
|
|
29
|
-
|
|
30
|
-
## Quick start (programmatic)
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
import { AwsSecretsManagerTools } from '@karmaniverous/aws-secrets-manager-tools';
|
|
34
|
-
|
|
35
|
-
const tools =
|
|
36
|
-
clientConfig: { region: 'us-east-1', logger: console },
|
|
37
|
-
xray: 'auto',
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const current = await tools.readEnvSecret({ secretId: 'my-app/dev' });
|
|
41
|
-
await tools.upsertEnvSecret({ secretId: 'my-app/dev', value: current });
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
When you need AWS functionality not wrapped by this package, use the fully configured AWS SDK v3 client at `tools.client` (see the [programmatic guide](guides/aws-secrets-manager-tools.md) for examples).
|
|
45
|
-
|
|
46
|
-
## Quick start (CLI)
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
aws-secrets-manager-tools --env dev aws secrets pull --secret-name '$STACK_NAME'
|
|
50
|
-
aws-secrets-manager-tools --env dev aws secrets push --secret-name '$STACK_NAME'
|
|
51
|
-
aws-secrets-manager-tools --env dev aws secrets delete --secret-name '$STACK_NAME'
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Notes:
|
|
55
|
-
|
|
56
|
-
- `--env` is a root-level (get-dotenv) option and must appear before the command path.
|
|
57
|
-
- Secret name expansion is evaluated at action time against `{ ...process.env, ...ctx.dotenv }` (ctx wins).
|
|
58
|
-
|
|
59
|
-
## Env-map secret format
|
|
60
|
-
|
|
61
|
-
Secrets are stored as a JSON object map of environment variables in `SecretString`:
|
|
62
|
-
|
|
63
|
-
```json
|
|
64
|
-
{ "KEY": "value", "OPTIONAL": null }
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Notes:
|
|
68
|
-
|
|
69
|
-
- Values must be strings or `null`.
|
|
70
|
-
- `null` is treated as `undefined` when decoding.
|
|
71
|
-
|
|
72
|
-
## AWS X-Ray capture (optional)
|
|
73
|
-
|
|
74
|
-
X-Ray support is guarded:
|
|
75
|
-
|
|
76
|
-
- Default behavior is `xray: 'auto'`: capture is enabled only when `AWS_XRAY_DAEMON_ADDRESS` is set.
|
|
77
|
-
- To enable capture, install the optional peer dependency:
|
|
78
|
-
- `aws-xray-sdk`
|
|
79
|
-
- In `auto` mode, if `AWS_XRAY_DAEMON_ADDRESS` is set but `aws-xray-sdk` is not installed,
|
|
80
|
-
|
|
81
|
-
## Config defaults (getdotenv.config.\*)
|
|
82
|
-
|
|
83
|
-
If you embed the plugin in your own get-dotenv host (or use the shipped CLI), you can provide safe defaults in config under `plugins['aws/secrets']`:
|
|
84
|
-
|
|
85
|
-
```jsonc
|
|
86
|
-
{
|
|
87
|
-
"plugins": {
|
|
88
|
-
"aws/secrets": {
|
|
89
|
-
"secretName": "$STACK_NAME",
|
|
90
|
-
"templateExtension": "template",
|
|
91
|
-
"push": { "from": ["file:env:private"] },
|
|
92
|
-
"pull": { "to": "env:private" },
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
See the [secrets plugin guide](guides/secrets-plugin.md) for `--from` / `--to` selector details and all supported config keys.
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
Built for you with ❤️ on Bali! Find more great tools & templates on [my GitHub Profile](https://github.com/karmaniverous).
|
|
1
|
+
# AWS Secrets Manager Tools
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@karmaniverous/aws-secrets-manager-tools)  [](https://docs.karmanivero.us/aws-secrets-manager-tools) [](./CHANGELOG.md) [](./LICENSE)
|
|
4
|
+
|
|
5
|
+
Tools and a get-dotenv plugin for working with AWS Secrets Manager “env-map” secrets (JSON object maps of environment variables).
|
|
6
|
+
|
|
7
|
+
This package provides:
|
|
8
|
+
|
|
9
|
+
- A tools-style wrapper that owns AWS client setup (including optional AWS X-Ray capture):
|
|
10
|
+
- `AwsSecretsManagerTools`
|
|
11
|
+
- A get-dotenv plugin intended to be mounted under `aws`:
|
|
12
|
+
- `secretsPlugin()` → `aws secrets pull|push|delete`
|
|
13
|
+
- A CLI embedding get-dotenv with the secrets plugin:
|
|
14
|
+
- `aws-secrets-manager-tools`
|
|
15
|
+
|
|
16
|
+
## Documentation
|
|
17
|
+
|
|
18
|
+
- Learn the programmatic API: [AwsSecretsManagerTools guide](guides/aws-secrets-manager-tools.md)
|
|
19
|
+
- Learn the CLI and plugin behavior: [aws secrets plugin guide](guides/secrets-plugin.md)
|
|
20
|
+
- Browse the generated API reference: [TypeDoc site](https://docs.karmanivero.us/aws-secrets-manager-tools)
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm i @karmaniverous/aws-secrets-manager-tools
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This package is ESM-only (Node >= 20).
|
|
29
|
+
|
|
30
|
+
## Quick start (programmatic)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { AwsSecretsManagerTools } from '@karmaniverous/aws-secrets-manager-tools';
|
|
34
|
+
|
|
35
|
+
const tools = new AwsSecretsManagerTools({
|
|
36
|
+
clientConfig: { region: 'us-east-1', logger: console },
|
|
37
|
+
xray: 'auto',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const current = await tools.readEnvSecret({ secretId: 'my-app/dev' });
|
|
41
|
+
await tools.upsertEnvSecret({ secretId: 'my-app/dev', value: current });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When you need AWS functionality not wrapped by this package, use the fully configured AWS SDK v3 client at `tools.client` (see the [programmatic guide](guides/aws-secrets-manager-tools.md) for examples).
|
|
45
|
+
|
|
46
|
+
## Quick start (CLI)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
aws-secrets-manager-tools --env dev aws secrets pull --secret-name '$STACK_NAME'
|
|
50
|
+
aws-secrets-manager-tools --env dev aws secrets push --secret-name '$STACK_NAME'
|
|
51
|
+
aws-secrets-manager-tools --env dev aws secrets delete --secret-name '$STACK_NAME'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Notes:
|
|
55
|
+
|
|
56
|
+
- `--env` is a root-level (get-dotenv) option and must appear before the command path.
|
|
57
|
+
- Secret name expansion is evaluated at action time against `{ ...process.env, ...ctx.dotenv }` (ctx wins).
|
|
58
|
+
|
|
59
|
+
## Env-map secret format
|
|
60
|
+
|
|
61
|
+
Secrets are stored as a JSON object map of environment variables in `SecretString`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{ "KEY": "value", "OPTIONAL": null }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Notes:
|
|
68
|
+
|
|
69
|
+
- Values must be strings or `null`.
|
|
70
|
+
- `null` is treated as `undefined` when decoding.
|
|
71
|
+
|
|
72
|
+
## AWS X-Ray capture (optional)
|
|
73
|
+
|
|
74
|
+
X-Ray support is guarded:
|
|
75
|
+
|
|
76
|
+
- Default behavior is `xray: 'auto'`: capture is enabled only when `AWS_XRAY_DAEMON_ADDRESS` is set.
|
|
77
|
+
- To enable capture, install the optional peer dependency:
|
|
78
|
+
- `aws-xray-sdk`
|
|
79
|
+
- In `auto` mode, if `AWS_XRAY_DAEMON_ADDRESS` is set but `aws-xray-sdk` is not installed, construction throws.
|
|
80
|
+
|
|
81
|
+
## Config defaults (getdotenv.config.\*)
|
|
82
|
+
|
|
83
|
+
If you embed the plugin in your own get-dotenv host (or use the shipped CLI), you can provide safe defaults in config under `plugins['aws/secrets']`:
|
|
84
|
+
|
|
85
|
+
```jsonc
|
|
86
|
+
{
|
|
87
|
+
"plugins": {
|
|
88
|
+
"aws/secrets": {
|
|
89
|
+
"secretName": "$STACK_NAME",
|
|
90
|
+
"templateExtension": "template",
|
|
91
|
+
"push": { "from": ["file:env:private"] },
|
|
92
|
+
"pull": { "to": "env:private" },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See the [secrets plugin guide](guides/secrets-plugin.md) for `--from` / `--to` selector details and all supported config keys.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
Built for you with ❤️ on Bali! Find more great tools & templates on [my GitHub Profile](https://github.com/karmaniverous).
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createCli } from '@karmaniverous/get-dotenv/cli';
|
|
3
|
-
import { cmdPlugin, batchPlugin, awsPlugin, initPlugin } from '@karmaniverous/get-dotenv/plugins';
|
|
4
|
-
import { readMergedOptions, z, definePlugin } from '@karmaniverous/get-dotenv/cliHost';
|
|
3
|
+
import { getAwsRegion, cmdPlugin, batchPlugin, awsPlugin, initPlugin } from '@karmaniverous/get-dotenv/plugins';
|
|
4
|
+
import { readMergedOptions, z, describeConfigKeyListDefaults, describeDefault, definePlugin } from '@karmaniverous/get-dotenv/cliHost';
|
|
5
|
+
import { assertLogger, silentLogger, buildSpawnEnv, dotenvExpand, toNumber, getDotenvCliOptions2Options, applyIncludeExclude, editDotenvFile, requireString, assertByteLimit } from '@karmaniverous/get-dotenv';
|
|
5
6
|
import { SecretsManagerClient, GetSecretValueCommand, PutSecretValueCommand, CreateSecretCommand, DeleteSecretCommand } from '@aws-sdk/client-secrets-manager';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { Buffer } from 'node:buffer';
|
|
7
|
+
import { shouldEnableXray, captureAwsSdkV3Client } from '@karmaniverous/aws-xray-tools';
|
|
8
|
+
import { getAwsRegion as getAwsRegion$1 } from '@karmaniverous/get-dotenv/plugins/aws';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Requirements addressed:
|
|
@@ -21,74 +21,22 @@ const getAwsErrorCode = (err) => {
|
|
|
21
21
|
const isAwsErrorCode = (err, code) => getAwsErrorCode(err) === code;
|
|
22
22
|
const isResourceNotFoundError = (err) => isAwsErrorCode(err, 'ResourceNotFoundException');
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
* Requirements addressed:
|
|
26
|
-
* - Optional AWS X-Ray capture support.
|
|
27
|
-
* - Default behavior “auto”: only attempt capture when AWS_XRAY_DAEMON_ADDRESS
|
|
28
|
-
* is set.
|
|
29
|
-
* - Avoid importing/enabling X-Ray when the daemon address is not set (the
|
|
30
|
-
* X-Ray SDK will throw otherwise).
|
|
31
|
-
*/
|
|
32
|
-
const shouldEnableXray = (mode, daemonAddress) => {
|
|
33
|
-
if (mode === 'off')
|
|
34
|
-
return false;
|
|
35
|
-
if (mode === 'on')
|
|
36
|
-
return true;
|
|
37
|
-
return Boolean(daemonAddress);
|
|
38
|
-
};
|
|
39
|
-
const captureAwsSdkV3Client = async (client, { mode = 'auto', logger = console, daemonAddress = process.env.AWS_XRAY_DAEMON_ADDRESS, } = {}) => {
|
|
40
|
-
if (!shouldEnableXray(mode, daemonAddress))
|
|
41
|
-
return client;
|
|
42
|
-
if (!daemonAddress) {
|
|
43
|
-
throw new Error('X-Ray capture requested but AWS_XRAY_DAEMON_ADDRESS is not set.');
|
|
44
|
-
}
|
|
45
|
-
// Guarded dynamic import: some X-Ray SDK integrations throw when daemon
|
|
46
|
-
// configuration is missing, so do not import unless we are capturing.
|
|
47
|
-
let mod;
|
|
48
|
-
try {
|
|
49
|
-
mod = (await import('aws-xray-sdk'));
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
throw new Error("X-Ray capture is enabled but 'aws-xray-sdk' is not installed. Install it or set xray to 'off'.");
|
|
53
|
-
}
|
|
54
|
-
const AWSXRay = (mod.default ?? mod);
|
|
55
|
-
if (typeof AWSXRay.captureAWSv3Client !== 'function') {
|
|
56
|
-
logger.debug('aws-xray-sdk does not expose captureAWSv3Client', AWSXRay);
|
|
57
|
-
throw new Error('aws-xray-sdk missing captureAWSv3Client export.');
|
|
58
|
-
}
|
|
59
|
-
logger.debug('Enabling AWS X-Ray capture for AWS SDK v3 client.');
|
|
60
|
-
return AWSXRay.captureAWSv3Client(client);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
24
|
/**
|
|
64
25
|
* Requirements addressed:
|
|
65
26
|
* - Provide a public tools-style wrapper `AwsSecretsManagerTools`.
|
|
66
27
|
* - Package consumers should not need to construct SecretsManagerClient; they
|
|
67
|
-
* should
|
|
68
|
-
* Commands for advanced operations.
|
|
28
|
+
* should construct `new AwsSecretsManagerTools(...)` and optionally import
|
|
29
|
+
* AWS SDK Commands for advanced operations.
|
|
69
30
|
* - Expose the fully configured SDK client via `tools.client`.
|
|
70
31
|
* - Support optional AWS X-Ray capture:
|
|
71
32
|
* - Default “auto”: enable only when AWS_XRAY_DAEMON_ADDRESS is set.
|
|
72
33
|
* - In “auto”, if the daemon address is set but aws-xray-sdk is missing,
|
|
73
34
|
* throw with a clear message.
|
|
74
|
-
* - Enforce
|
|
75
|
-
*
|
|
35
|
+
* - Enforce the get-dotenv minimal Logger contract (debug/info/warn/error);
|
|
36
|
+
* validate and throw (no polyfills or proxies).
|
|
76
37
|
* - Secret values are JSON object maps of env vars.
|
|
77
38
|
*/
|
|
78
|
-
const
|
|
79
|
-
if (!candidate || typeof candidate !== 'object') {
|
|
80
|
-
throw new Error('logger must be an object with debug, info, warn, and error methods');
|
|
81
|
-
}
|
|
82
|
-
const logger = candidate;
|
|
83
|
-
if (typeof logger.debug !== 'function' ||
|
|
84
|
-
typeof logger.info !== 'function' ||
|
|
85
|
-
typeof logger.warn !== 'function' ||
|
|
86
|
-
typeof logger.error !== 'function') {
|
|
87
|
-
throw new Error('logger must implement debug, info, warn, and error methods; wrap/proxy your logger if needed');
|
|
88
|
-
}
|
|
89
|
-
return logger;
|
|
90
|
-
};
|
|
91
|
-
const parseEnvSecretMap = (secretString) => {
|
|
39
|
+
const parseProcessEnv = (secretString) => {
|
|
92
40
|
let parsed;
|
|
93
41
|
try {
|
|
94
42
|
parsed = JSON.parse(secretString);
|
|
@@ -118,7 +66,7 @@ const toSecretString = (value) => JSON.stringify(value);
|
|
|
118
66
|
* Tools-style AWS Secrets Manager wrapper for env-map secrets.
|
|
119
67
|
*
|
|
120
68
|
* The secret payload is always a JSON object map of environment variables:
|
|
121
|
-
* `
|
|
69
|
+
* `ProcessEnv`.
|
|
122
70
|
*
|
|
123
71
|
* Consumers should typically use the convenience methods on this class, and
|
|
124
72
|
* use {@link AwsSecretsManagerTools.client} as an escape hatch when they need
|
|
@@ -141,17 +89,8 @@ class AwsSecretsManagerTools {
|
|
|
141
89
|
logger;
|
|
142
90
|
/** Materialized X-Ray state (mode + enabled + daemonAddress when relevant). */
|
|
143
91
|
xray;
|
|
144
|
-
constructor({ client, clientConfig, logger, xray, }) {
|
|
145
|
-
this.client = client;
|
|
146
|
-
this.clientConfig = clientConfig;
|
|
147
|
-
this.logger = logger;
|
|
148
|
-
this.xray = xray;
|
|
149
|
-
}
|
|
150
92
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* This factory owns all setup (including optional X-Ray capture) so consumers
|
|
154
|
-
* do not need to construct a base Secrets Manager client themselves.
|
|
93
|
+
* Construct an `AwsSecretsManagerTools` instance.
|
|
155
94
|
*
|
|
156
95
|
* @throws If `clientConfig.logger` is provided but does not implement
|
|
157
96
|
* `debug`, `info`, `warn`, and `error`.
|
|
@@ -159,7 +98,7 @@ class AwsSecretsManagerTools {
|
|
|
159
98
|
* with `AWS_XRAY_DAEMON_ADDRESS` set) but `aws-xray-sdk` is not installed.
|
|
160
99
|
* @throws If X-Ray capture is requested but `AWS_XRAY_DAEMON_ADDRESS` is not set.
|
|
161
100
|
*/
|
|
162
|
-
|
|
101
|
+
constructor({ clientConfig = {}, xray: xrayMode = 'auto', } = {}) {
|
|
163
102
|
const logger = assertLogger(clientConfig.logger ?? console);
|
|
164
103
|
const effectiveClientConfig = {
|
|
165
104
|
...clientConfig,
|
|
@@ -174,18 +113,16 @@ class AwsSecretsManagerTools {
|
|
|
174
113
|
...(enabled && daemonAddress ? { daemonAddress } : {}),
|
|
175
114
|
};
|
|
176
115
|
const effectiveClient = enabled
|
|
177
|
-
?
|
|
116
|
+
? captureAwsSdkV3Client(base, {
|
|
178
117
|
mode: xrayMode,
|
|
179
118
|
logger,
|
|
180
119
|
daemonAddress,
|
|
181
120
|
})
|
|
182
121
|
: base;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
xray: xrayState,
|
|
188
|
-
});
|
|
122
|
+
this.client = effectiveClient;
|
|
123
|
+
this.clientConfig = effectiveClientConfig;
|
|
124
|
+
this.logger = logger;
|
|
125
|
+
this.xray = xrayState;
|
|
189
126
|
}
|
|
190
127
|
/**
|
|
191
128
|
* Read a Secrets Manager secret and parse it as an env-map secret.
|
|
@@ -208,7 +145,7 @@ class AwsSecretsManagerTools {
|
|
|
208
145
|
if (!res.SecretString) {
|
|
209
146
|
throw new Error('SecretString is missing (binary secrets not supported).');
|
|
210
147
|
}
|
|
211
|
-
return
|
|
148
|
+
return parseProcessEnv(res.SecretString);
|
|
212
149
|
}
|
|
213
150
|
/**
|
|
214
151
|
* Write a new version value for an existing secret.
|
|
@@ -315,96 +252,6 @@ class AwsSecretsManagerTools {
|
|
|
315
252
|
}
|
|
316
253
|
}
|
|
317
254
|
|
|
318
|
-
/**
|
|
319
|
-
* Requirements addressed:
|
|
320
|
-
* - Secret name expansion expands against `{ ...process.env, ...ctx.dotenv }`.
|
|
321
|
-
* - include/exclude ignore unknown keys; use radash (no lodash).
|
|
322
|
-
*/
|
|
323
|
-
const buildExpansionEnv = (ctxDotenv) => ({
|
|
324
|
-
...process.env,
|
|
325
|
-
...ctxDotenv,
|
|
326
|
-
});
|
|
327
|
-
const expandSecretName = (raw, envRef) => dotenvExpand(raw, envRef) ?? raw;
|
|
328
|
-
const applyIncludeExclude = (env, { include, exclude, }) => {
|
|
329
|
-
let out = env;
|
|
330
|
-
if (exclude?.length)
|
|
331
|
-
out = omit(out, exclude);
|
|
332
|
-
if (include?.length)
|
|
333
|
-
out = pick(out, include);
|
|
334
|
-
return out;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Requirements addressed:
|
|
339
|
-
* - Enforce AWS Secrets Manager SecretString size limits (65,536 bytes).
|
|
340
|
-
* - Provide safe parsing helpers for CLI-mapped inputs.
|
|
341
|
-
* - Render config-derived defaults in dynamic option help text.
|
|
342
|
-
* - Access aws plugin ctx state via runtime narrowing (no casts).
|
|
343
|
-
*/
|
|
344
|
-
const silentLogger = {
|
|
345
|
-
debug: () => {
|
|
346
|
-
// no-op
|
|
347
|
-
},
|
|
348
|
-
info: () => {
|
|
349
|
-
// no-op
|
|
350
|
-
},
|
|
351
|
-
warn: () => {
|
|
352
|
-
// no-op
|
|
353
|
-
},
|
|
354
|
-
error: () => {
|
|
355
|
-
// no-op
|
|
356
|
-
},
|
|
357
|
-
};
|
|
358
|
-
const requireString = (v, msg) => {
|
|
359
|
-
if (typeof v !== 'string' || !v)
|
|
360
|
-
throw new Error(msg);
|
|
361
|
-
return v;
|
|
362
|
-
};
|
|
363
|
-
const toNumber = (v) => {
|
|
364
|
-
if (typeof v === 'undefined')
|
|
365
|
-
return;
|
|
366
|
-
if (typeof v === 'number')
|
|
367
|
-
return v;
|
|
368
|
-
if (typeof v === 'string' && v.trim())
|
|
369
|
-
return Number(v);
|
|
370
|
-
return;
|
|
371
|
-
};
|
|
372
|
-
const assertBytesWithinSecretsManagerLimit = (value) => {
|
|
373
|
-
const s = JSON.stringify(value);
|
|
374
|
-
const bytes = Buffer.byteLength(s, 'utf8');
|
|
375
|
-
if (bytes > 65_536) {
|
|
376
|
-
throw new Error(`SecretString size ${String(bytes)} bytes exceeds 65536 bytes; narrow selection with --from/--include/--exclude.`);
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
const describeDefault = (v) => {
|
|
380
|
-
if (Array.isArray(v))
|
|
381
|
-
return v.length ? v.join(' ') : 'none';
|
|
382
|
-
if (typeof v === 'string' && v.trim())
|
|
383
|
-
return v;
|
|
384
|
-
return 'none';
|
|
385
|
-
};
|
|
386
|
-
const isRecord = (v) => typeof v === 'object' && v !== null;
|
|
387
|
-
const getAwsRegion = (ctx) => {
|
|
388
|
-
if (!isRecord(ctx.plugins))
|
|
389
|
-
return;
|
|
390
|
-
const aws = ctx.plugins['aws'];
|
|
391
|
-
if (!isRecord(aws))
|
|
392
|
-
return;
|
|
393
|
-
const region = aws['region'];
|
|
394
|
-
return typeof region === 'string' ? region : undefined;
|
|
395
|
-
};
|
|
396
|
-
const describeConfigKeyListDefaults = ({ cfgInclude, cfgExclude, }) => {
|
|
397
|
-
// Avoid throwing in help rendering: show an explicit invalid marker.
|
|
398
|
-
if (cfgInclude?.length && cfgExclude?.length) {
|
|
399
|
-
const msg = '(invalid: both set in config)';
|
|
400
|
-
return { includeDefault: msg, excludeDefault: msg };
|
|
401
|
-
}
|
|
402
|
-
return {
|
|
403
|
-
includeDefault: describeDefault(cfgExclude?.length ? undefined : cfgInclude),
|
|
404
|
-
excludeDefault: describeDefault(cfgInclude?.length ? undefined : cfgExclude),
|
|
405
|
-
};
|
|
406
|
-
};
|
|
407
|
-
|
|
408
255
|
/**
|
|
409
256
|
* Requirements addressed:
|
|
410
257
|
* - Provide `aws secrets delete`.
|
|
@@ -434,14 +281,14 @@ const registerDeleteCommand = ({ cli, plugin, }) => {
|
|
|
434
281
|
const logger = console;
|
|
435
282
|
const ctx = cli.getCtx();
|
|
436
283
|
const cfg = plugin.readConfig(del);
|
|
437
|
-
const envRef =
|
|
284
|
+
const envRef = buildSpawnEnv(process.env, ctx.dotenv);
|
|
438
285
|
const secretNameRaw = opts.secretName ?? cfg.secretName ?? '$STACK_NAME';
|
|
439
|
-
const secretId =
|
|
286
|
+
const secretId = dotenvExpand(secretNameRaw, envRef);
|
|
440
287
|
if (!secretId)
|
|
441
288
|
throw new Error('secret-name is required.');
|
|
442
289
|
const recoveryWindowInDays = toNumber(opts.recoveryWindowDays);
|
|
443
290
|
const region = getAwsRegion(ctx);
|
|
444
|
-
const tools =
|
|
291
|
+
const tools = new AwsSecretsManagerTools({
|
|
445
292
|
clientConfig: region
|
|
446
293
|
? { region, logger: sdkLogger }
|
|
447
294
|
: { logger: sdkLogger },
|
|
@@ -718,13 +565,13 @@ const registerPullCommand = ({ cli, plugin, }) => {
|
|
|
718
565
|
const privateToken = rootOpts.privateToken ?? 'local';
|
|
719
566
|
const toRaw = opts.to ?? cfg.pull?.to ?? 'env:private';
|
|
720
567
|
const to = parseToSelector(toRaw);
|
|
721
|
-
const envRef =
|
|
568
|
+
const envRef = buildSpawnEnv(process.env, ctx.dotenv);
|
|
722
569
|
const secretNameRaw = opts.secretName ?? cfg.secretName ?? '$STACK_NAME';
|
|
723
|
-
const secretId =
|
|
570
|
+
const secretId = dotenvExpand(secretNameRaw, envRef);
|
|
724
571
|
if (!secretId)
|
|
725
572
|
throw new Error('secret-name is required.');
|
|
726
|
-
const region = getAwsRegion(ctx);
|
|
727
|
-
const tools =
|
|
573
|
+
const region = getAwsRegion$1(ctx);
|
|
574
|
+
const tools = new AwsSecretsManagerTools({
|
|
728
575
|
clientConfig: region
|
|
729
576
|
? { region, logger: sdkLogger }
|
|
730
577
|
: { logger: sdkLogger },
|
|
@@ -750,7 +597,7 @@ const registerPullCommand = ({ cli, plugin, }) => {
|
|
|
750
597
|
? await editDotenvFile(secrets, {
|
|
751
598
|
...editCommon,
|
|
752
599
|
scope: 'env',
|
|
753
|
-
env: requireString(bag.env
|
|
600
|
+
env: requireString(bag.env, 'env is required (use --env or defaultEnv).'),
|
|
754
601
|
})
|
|
755
602
|
: await editDotenvFile(secrets, {
|
|
756
603
|
...editCommon,
|
|
@@ -823,16 +670,16 @@ const registerPushCommand = ({ cli, plugin, }) => {
|
|
|
823
670
|
cfgInclude: cfg.push?.include,
|
|
824
671
|
cfgExclude: cfg.push?.exclude,
|
|
825
672
|
});
|
|
826
|
-
const envRef =
|
|
673
|
+
const envRef = buildSpawnEnv(process.env, ctx.dotenv);
|
|
827
674
|
const secretNameRaw = opts.secretName ?? cfg.secretName ?? '$STACK_NAME';
|
|
828
|
-
const secretId =
|
|
675
|
+
const secretId = dotenvExpand(secretNameRaw, envRef);
|
|
829
676
|
if (!secretId)
|
|
830
677
|
throw new Error('secret-name is required.');
|
|
831
678
|
const selected = selectEnvByProvenance(ctx.dotenv, ctx.dotenvProvenance, fromSelectors);
|
|
832
679
|
const secrets = applyIncludeExclude(selected, { include, exclude });
|
|
833
|
-
|
|
834
|
-
const region = getAwsRegion(ctx);
|
|
835
|
-
const tools =
|
|
680
|
+
assertByteLimit(secrets, 65_536, (v, l) => `SecretString size ${String(v)} bytes exceeds ${String(l)} bytes; narrow selection with --from/--include/--exclude.`);
|
|
681
|
+
const region = getAwsRegion$1(ctx);
|
|
682
|
+
const tools = new AwsSecretsManagerTools({
|
|
836
683
|
clientConfig: region
|
|
837
684
|
? { region, logger: sdkLogger }
|
|
838
685
|
: { logger: sdkLogger },
|
package/dist/index.d.ts
CHANGED
|
@@ -1,59 +1,26 @@
|
|
|
1
1
|
import { SecretsManagerClient, SecretsManagerClientConfig } from '@aws-sdk/client-secrets-manager';
|
|
2
|
+
import { XrayState, XrayMode } from '@karmaniverous/aws-xray-tools';
|
|
3
|
+
import { Logger, ProcessEnv } from '@karmaniverous/get-dotenv';
|
|
2
4
|
import * as _karmaniverous_get_dotenv_cliHost from '@karmaniverous/get-dotenv/cliHost';
|
|
3
5
|
|
|
4
|
-
/**
|
|
5
|
-
* Requirements addressed:
|
|
6
|
-
* - Secret values are always a JSON object map of env vars.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Canonical “env secret” shape stored in AWS Secrets Manager.
|
|
10
|
-
*
|
|
11
|
-
* `undefined` values are not representable in JSON; readers should treat `null`
|
|
12
|
-
* values as `undefined` when decoding.
|
|
13
|
-
*/
|
|
14
|
-
type EnvSecretMap = Record<string, string | undefined>;
|
|
15
|
-
|
|
16
6
|
/**
|
|
17
7
|
* Requirements addressed:
|
|
18
8
|
* - Provide a public tools-style wrapper `AwsSecretsManagerTools`.
|
|
19
9
|
* - Package consumers should not need to construct SecretsManagerClient; they
|
|
20
|
-
* should
|
|
21
|
-
* Commands for advanced operations.
|
|
10
|
+
* should construct `new AwsSecretsManagerTools(...)` and optionally import
|
|
11
|
+
* AWS SDK Commands for advanced operations.
|
|
22
12
|
* - Expose the fully configured SDK client via `tools.client`.
|
|
23
13
|
* - Support optional AWS X-Ray capture:
|
|
24
14
|
* - Default “auto”: enable only when AWS_XRAY_DAEMON_ADDRESS is set.
|
|
25
15
|
* - In “auto”, if the daemon address is set but aws-xray-sdk is missing,
|
|
26
16
|
* throw with a clear message.
|
|
27
|
-
* - Enforce
|
|
28
|
-
*
|
|
17
|
+
* - Enforce the get-dotenv minimal Logger contract (debug/info/warn/error);
|
|
18
|
+
* validate and throw (no polyfills or proxies).
|
|
29
19
|
* - Secret values are JSON object maps of env vars.
|
|
30
20
|
*/
|
|
31
21
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
* If you pass a custom logger via `clientConfig.logger`, it must implement
|
|
36
|
-
* these methods (no internal polyfills are applied).
|
|
37
|
-
*/
|
|
38
|
-
type AwsSecretsManagerToolsLogger = Pick<Console, 'debug' | 'error' | 'info' | 'warn'>;
|
|
39
|
-
/** X-Ray capture mode for {@link AwsSecretsManagerTools.init}. */
|
|
40
|
-
type AwsSecretsManagerToolsXrayMode = 'auto' | 'on' | 'off';
|
|
41
|
-
/**
|
|
42
|
-
* Materialized X-Ray state for diagnostics and DX.
|
|
43
|
-
*
|
|
44
|
-
* Note: `enabled` reflects the effective runtime decision after applying the
|
|
45
|
-
* configured `mode` and checking daemon configuration.
|
|
46
|
-
*/
|
|
47
|
-
type XrayState = {
|
|
48
|
-
/** Capture mode configured for initialization. */
|
|
49
|
-
mode: AwsSecretsManagerToolsXrayMode;
|
|
50
|
-
/** Whether capture is enabled for the effective client instance. */
|
|
51
|
-
enabled: boolean;
|
|
52
|
-
/** Daemon address used when capture is enabled (if available). */
|
|
53
|
-
daemonAddress?: string;
|
|
54
|
-
};
|
|
55
|
-
/** Options for {@link AwsSecretsManagerTools.init}. */
|
|
56
|
-
type AwsSecretsManagerToolsInitOptions = {
|
|
22
|
+
/** Options for {@link AwsSecretsManagerTools} construction. */
|
|
23
|
+
type AwsSecretsManagerToolsOptions = {
|
|
57
24
|
/**
|
|
58
25
|
* AWS SDK v3 Secrets Manager client config.
|
|
59
26
|
*
|
|
@@ -69,13 +36,13 @@ type AwsSecretsManagerToolsInitOptions = {
|
|
|
69
36
|
* - `on`: force enable (throws if daemon address is missing).
|
|
70
37
|
* - `off`: disable.
|
|
71
38
|
*/
|
|
72
|
-
xray?:
|
|
39
|
+
xray?: XrayMode;
|
|
73
40
|
};
|
|
74
41
|
/**
|
|
75
42
|
* Tools-style AWS Secrets Manager wrapper for env-map secrets.
|
|
76
43
|
*
|
|
77
44
|
* The secret payload is always a JSON object map of environment variables:
|
|
78
|
-
* `
|
|
45
|
+
* `ProcessEnv`.
|
|
79
46
|
*
|
|
80
47
|
* Consumers should typically use the convenience methods on this class, and
|
|
81
48
|
* use {@link AwsSecretsManagerTools.client} as an escape hatch when they need
|
|
@@ -95,15 +62,11 @@ declare class AwsSecretsManagerTools {
|
|
|
95
62
|
*/
|
|
96
63
|
readonly clientConfig: SecretsManagerClientConfig;
|
|
97
64
|
/** The logger used by this wrapper and (when applicable) by the AWS client. */
|
|
98
|
-
readonly logger:
|
|
65
|
+
readonly logger: Logger;
|
|
99
66
|
/** Materialized X-Ray state (mode + enabled + daemonAddress when relevant). */
|
|
100
67
|
readonly xray: XrayState;
|
|
101
|
-
private constructor();
|
|
102
68
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* This factory owns all setup (including optional X-Ray capture) so consumers
|
|
106
|
-
* do not need to construct a base Secrets Manager client themselves.
|
|
69
|
+
* Construct an `AwsSecretsManagerTools` instance.
|
|
107
70
|
*
|
|
108
71
|
* @throws If `clientConfig.logger` is provided but does not implement
|
|
109
72
|
* `debug`, `info`, `warn`, and `error`.
|
|
@@ -111,7 +74,7 @@ declare class AwsSecretsManagerTools {
|
|
|
111
74
|
* with `AWS_XRAY_DAEMON_ADDRESS` set) but `aws-xray-sdk` is not installed.
|
|
112
75
|
* @throws If X-Ray capture is requested but `AWS_XRAY_DAEMON_ADDRESS` is not set.
|
|
113
76
|
*/
|
|
114
|
-
|
|
77
|
+
constructor({ clientConfig, xray: xrayMode, }?: AwsSecretsManagerToolsOptions);
|
|
115
78
|
/**
|
|
116
79
|
* Read a Secrets Manager secret and parse it as an env-map secret.
|
|
117
80
|
*
|
|
@@ -124,7 +87,7 @@ declare class AwsSecretsManagerTools {
|
|
|
124
87
|
readEnvSecret(opts: {
|
|
125
88
|
secretId: string;
|
|
126
89
|
versionId?: string;
|
|
127
|
-
}): Promise<
|
|
90
|
+
}): Promise<ProcessEnv>;
|
|
128
91
|
/**
|
|
129
92
|
* Write a new version value for an existing secret.
|
|
130
93
|
*
|
|
@@ -137,7 +100,7 @@ declare class AwsSecretsManagerTools {
|
|
|
137
100
|
*/
|
|
138
101
|
updateEnvSecret(opts: {
|
|
139
102
|
secretId: string;
|
|
140
|
-
value:
|
|
103
|
+
value: ProcessEnv;
|
|
141
104
|
versionId?: string;
|
|
142
105
|
}): Promise<void>;
|
|
143
106
|
/**
|
|
@@ -152,7 +115,7 @@ declare class AwsSecretsManagerTools {
|
|
|
152
115
|
*/
|
|
153
116
|
createEnvSecret(opts: {
|
|
154
117
|
secretId: string;
|
|
155
|
-
value:
|
|
118
|
+
value: ProcessEnv;
|
|
156
119
|
description?: string;
|
|
157
120
|
forceOverwriteReplicaSecret?: boolean;
|
|
158
121
|
versionId?: string;
|
|
@@ -168,7 +131,7 @@ declare class AwsSecretsManagerTools {
|
|
|
168
131
|
*/
|
|
169
132
|
upsertEnvSecret({ secretId, value, }: {
|
|
170
133
|
secretId: string;
|
|
171
|
-
value:
|
|
134
|
+
value: ProcessEnv;
|
|
172
135
|
}): Promise<'updated' | 'created'>;
|
|
173
136
|
/**
|
|
174
137
|
* Delete a secret.
|
|
@@ -209,6 +172,7 @@ declare class AwsSecretsManagerTools {
|
|
|
209
172
|
declare const secretsPlugin: () => _karmaniverous_get_dotenv_cliHost.PluginWithInstanceHelpers<Omit<{
|
|
210
173
|
logger: unknown;
|
|
211
174
|
defaultEnv?: string | undefined;
|
|
175
|
+
defaultEnvKey?: string | undefined;
|
|
212
176
|
dotenvToken?: string | undefined;
|
|
213
177
|
dynamicPath?: string | undefined;
|
|
214
178
|
dynamic?: Record<string, unknown> | undefined;
|
|
@@ -225,7 +189,12 @@ declare const secretsPlugin: () => _karmaniverous_get_dotenv_cliHost.PluginWithI
|
|
|
225
189
|
privateToken?: string | undefined;
|
|
226
190
|
vars?: Record<string, string | undefined> | undefined;
|
|
227
191
|
}, "dynamic" | "logger"> & {
|
|
228
|
-
logger:
|
|
192
|
+
logger: {
|
|
193
|
+
debug: (...data: any[]) => void;
|
|
194
|
+
info: (...data: any[]) => void;
|
|
195
|
+
warn: (...data: any[]) => void;
|
|
196
|
+
error: (...data: any[]) => void;
|
|
197
|
+
};
|
|
229
198
|
dynamic?: {
|
|
230
199
|
[x: string]: string | ((vars: {
|
|
231
200
|
[x: string]: string | undefined;
|
|
@@ -247,4 +216,4 @@ declare const secretsPlugin: () => _karmaniverous_get_dotenv_cliHost.PluginWithI
|
|
|
247
216
|
}, [], {}, {}>;
|
|
248
217
|
|
|
249
218
|
export { AwsSecretsManagerTools, secretsPlugin };
|
|
250
|
-
export type {
|
|
219
|
+
export type { AwsSecretsManagerToolsOptions };
|
package/dist/mjs/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SecretsManagerClient, GetSecretValueCommand, PutSecretValueCommand, CreateSecretCommand, DeleteSecretCommand } from '@aws-sdk/client-secrets-manager';
|
|
2
|
-
import {
|
|
3
|
-
import { dotenvExpand, getDotenvCliOptions2Options, editDotenvFile } from '@karmaniverous/get-dotenv';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { shouldEnableXray, captureAwsSdkV3Client } from '@karmaniverous/aws-xray-tools';
|
|
3
|
+
import { assertLogger, silentLogger, buildSpawnEnv, dotenvExpand, toNumber, getDotenvCliOptions2Options, applyIncludeExclude, editDotenvFile, requireString, assertByteLimit } from '@karmaniverous/get-dotenv';
|
|
4
|
+
import { readMergedOptions, z, describeConfigKeyListDefaults, describeDefault, definePlugin } from '@karmaniverous/get-dotenv/cliHost';
|
|
5
|
+
import { getAwsRegion } from '@karmaniverous/get-dotenv/plugins';
|
|
6
|
+
import { getAwsRegion as getAwsRegion$1 } from '@karmaniverous/get-dotenv/plugins/aws';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Requirements addressed:
|
|
@@ -18,74 +19,22 @@ const getAwsErrorCode = (err) => {
|
|
|
18
19
|
const isAwsErrorCode = (err, code) => getAwsErrorCode(err) === code;
|
|
19
20
|
const isResourceNotFoundError = (err) => isAwsErrorCode(err, 'ResourceNotFoundException');
|
|
20
21
|
|
|
21
|
-
/**
|
|
22
|
-
* Requirements addressed:
|
|
23
|
-
* - Optional AWS X-Ray capture support.
|
|
24
|
-
* - Default behavior “auto”: only attempt capture when AWS_XRAY_DAEMON_ADDRESS
|
|
25
|
-
* is set.
|
|
26
|
-
* - Avoid importing/enabling X-Ray when the daemon address is not set (the
|
|
27
|
-
* X-Ray SDK will throw otherwise).
|
|
28
|
-
*/
|
|
29
|
-
const shouldEnableXray = (mode, daemonAddress) => {
|
|
30
|
-
if (mode === 'off')
|
|
31
|
-
return false;
|
|
32
|
-
if (mode === 'on')
|
|
33
|
-
return true;
|
|
34
|
-
return Boolean(daemonAddress);
|
|
35
|
-
};
|
|
36
|
-
const captureAwsSdkV3Client = async (client, { mode = 'auto', logger = console, daemonAddress = process.env.AWS_XRAY_DAEMON_ADDRESS, } = {}) => {
|
|
37
|
-
if (!shouldEnableXray(mode, daemonAddress))
|
|
38
|
-
return client;
|
|
39
|
-
if (!daemonAddress) {
|
|
40
|
-
throw new Error('X-Ray capture requested but AWS_XRAY_DAEMON_ADDRESS is not set.');
|
|
41
|
-
}
|
|
42
|
-
// Guarded dynamic import: some X-Ray SDK integrations throw when daemon
|
|
43
|
-
// configuration is missing, so do not import unless we are capturing.
|
|
44
|
-
let mod;
|
|
45
|
-
try {
|
|
46
|
-
mod = (await import('aws-xray-sdk'));
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
throw new Error("X-Ray capture is enabled but 'aws-xray-sdk' is not installed. Install it or set xray to 'off'.");
|
|
50
|
-
}
|
|
51
|
-
const AWSXRay = (mod.default ?? mod);
|
|
52
|
-
if (typeof AWSXRay.captureAWSv3Client !== 'function') {
|
|
53
|
-
logger.debug('aws-xray-sdk does not expose captureAWSv3Client', AWSXRay);
|
|
54
|
-
throw new Error('aws-xray-sdk missing captureAWSv3Client export.');
|
|
55
|
-
}
|
|
56
|
-
logger.debug('Enabling AWS X-Ray capture for AWS SDK v3 client.');
|
|
57
|
-
return AWSXRay.captureAWSv3Client(client);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
22
|
/**
|
|
61
23
|
* Requirements addressed:
|
|
62
24
|
* - Provide a public tools-style wrapper `AwsSecretsManagerTools`.
|
|
63
25
|
* - Package consumers should not need to construct SecretsManagerClient; they
|
|
64
|
-
* should
|
|
65
|
-
* Commands for advanced operations.
|
|
26
|
+
* should construct `new AwsSecretsManagerTools(...)` and optionally import
|
|
27
|
+
* AWS SDK Commands for advanced operations.
|
|
66
28
|
* - Expose the fully configured SDK client via `tools.client`.
|
|
67
29
|
* - Support optional AWS X-Ray capture:
|
|
68
30
|
* - Default “auto”: enable only when AWS_XRAY_DAEMON_ADDRESS is set.
|
|
69
31
|
* - In “auto”, if the daemon address is set but aws-xray-sdk is missing,
|
|
70
32
|
* throw with a clear message.
|
|
71
|
-
* - Enforce
|
|
72
|
-
*
|
|
33
|
+
* - Enforce the get-dotenv minimal Logger contract (debug/info/warn/error);
|
|
34
|
+
* validate and throw (no polyfills or proxies).
|
|
73
35
|
* - Secret values are JSON object maps of env vars.
|
|
74
36
|
*/
|
|
75
|
-
const
|
|
76
|
-
if (!candidate || typeof candidate !== 'object') {
|
|
77
|
-
throw new Error('logger must be an object with debug, info, warn, and error methods');
|
|
78
|
-
}
|
|
79
|
-
const logger = candidate;
|
|
80
|
-
if (typeof logger.debug !== 'function' ||
|
|
81
|
-
typeof logger.info !== 'function' ||
|
|
82
|
-
typeof logger.warn !== 'function' ||
|
|
83
|
-
typeof logger.error !== 'function') {
|
|
84
|
-
throw new Error('logger must implement debug, info, warn, and error methods; wrap/proxy your logger if needed');
|
|
85
|
-
}
|
|
86
|
-
return logger;
|
|
87
|
-
};
|
|
88
|
-
const parseEnvSecretMap = (secretString) => {
|
|
37
|
+
const parseProcessEnv = (secretString) => {
|
|
89
38
|
let parsed;
|
|
90
39
|
try {
|
|
91
40
|
parsed = JSON.parse(secretString);
|
|
@@ -115,7 +64,7 @@ const toSecretString = (value) => JSON.stringify(value);
|
|
|
115
64
|
* Tools-style AWS Secrets Manager wrapper for env-map secrets.
|
|
116
65
|
*
|
|
117
66
|
* The secret payload is always a JSON object map of environment variables:
|
|
118
|
-
* `
|
|
67
|
+
* `ProcessEnv`.
|
|
119
68
|
*
|
|
120
69
|
* Consumers should typically use the convenience methods on this class, and
|
|
121
70
|
* use {@link AwsSecretsManagerTools.client} as an escape hatch when they need
|
|
@@ -138,17 +87,8 @@ class AwsSecretsManagerTools {
|
|
|
138
87
|
logger;
|
|
139
88
|
/** Materialized X-Ray state (mode + enabled + daemonAddress when relevant). */
|
|
140
89
|
xray;
|
|
141
|
-
constructor({ client, clientConfig, logger, xray, }) {
|
|
142
|
-
this.client = client;
|
|
143
|
-
this.clientConfig = clientConfig;
|
|
144
|
-
this.logger = logger;
|
|
145
|
-
this.xray = xray;
|
|
146
|
-
}
|
|
147
90
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* This factory owns all setup (including optional X-Ray capture) so consumers
|
|
151
|
-
* do not need to construct a base Secrets Manager client themselves.
|
|
91
|
+
* Construct an `AwsSecretsManagerTools` instance.
|
|
152
92
|
*
|
|
153
93
|
* @throws If `clientConfig.logger` is provided but does not implement
|
|
154
94
|
* `debug`, `info`, `warn`, and `error`.
|
|
@@ -156,7 +96,7 @@ class AwsSecretsManagerTools {
|
|
|
156
96
|
* with `AWS_XRAY_DAEMON_ADDRESS` set) but `aws-xray-sdk` is not installed.
|
|
157
97
|
* @throws If X-Ray capture is requested but `AWS_XRAY_DAEMON_ADDRESS` is not set.
|
|
158
98
|
*/
|
|
159
|
-
|
|
99
|
+
constructor({ clientConfig = {}, xray: xrayMode = 'auto', } = {}) {
|
|
160
100
|
const logger = assertLogger(clientConfig.logger ?? console);
|
|
161
101
|
const effectiveClientConfig = {
|
|
162
102
|
...clientConfig,
|
|
@@ -171,18 +111,16 @@ class AwsSecretsManagerTools {
|
|
|
171
111
|
...(enabled && daemonAddress ? { daemonAddress } : {}),
|
|
172
112
|
};
|
|
173
113
|
const effectiveClient = enabled
|
|
174
|
-
?
|
|
114
|
+
? captureAwsSdkV3Client(base, {
|
|
175
115
|
mode: xrayMode,
|
|
176
116
|
logger,
|
|
177
117
|
daemonAddress,
|
|
178
118
|
})
|
|
179
119
|
: base;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
xray: xrayState,
|
|
185
|
-
});
|
|
120
|
+
this.client = effectiveClient;
|
|
121
|
+
this.clientConfig = effectiveClientConfig;
|
|
122
|
+
this.logger = logger;
|
|
123
|
+
this.xray = xrayState;
|
|
186
124
|
}
|
|
187
125
|
/**
|
|
188
126
|
* Read a Secrets Manager secret and parse it as an env-map secret.
|
|
@@ -205,7 +143,7 @@ class AwsSecretsManagerTools {
|
|
|
205
143
|
if (!res.SecretString) {
|
|
206
144
|
throw new Error('SecretString is missing (binary secrets not supported).');
|
|
207
145
|
}
|
|
208
|
-
return
|
|
146
|
+
return parseProcessEnv(res.SecretString);
|
|
209
147
|
}
|
|
210
148
|
/**
|
|
211
149
|
* Write a new version value for an existing secret.
|
|
@@ -312,96 +250,6 @@ class AwsSecretsManagerTools {
|
|
|
312
250
|
}
|
|
313
251
|
}
|
|
314
252
|
|
|
315
|
-
/**
|
|
316
|
-
* Requirements addressed:
|
|
317
|
-
* - Secret name expansion expands against `{ ...process.env, ...ctx.dotenv }`.
|
|
318
|
-
* - include/exclude ignore unknown keys; use radash (no lodash).
|
|
319
|
-
*/
|
|
320
|
-
const buildExpansionEnv = (ctxDotenv) => ({
|
|
321
|
-
...process.env,
|
|
322
|
-
...ctxDotenv,
|
|
323
|
-
});
|
|
324
|
-
const expandSecretName = (raw, envRef) => dotenvExpand(raw, envRef) ?? raw;
|
|
325
|
-
const applyIncludeExclude = (env, { include, exclude, }) => {
|
|
326
|
-
let out = env;
|
|
327
|
-
if (exclude?.length)
|
|
328
|
-
out = omit(out, exclude);
|
|
329
|
-
if (include?.length)
|
|
330
|
-
out = pick(out, include);
|
|
331
|
-
return out;
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Requirements addressed:
|
|
336
|
-
* - Enforce AWS Secrets Manager SecretString size limits (65,536 bytes).
|
|
337
|
-
* - Provide safe parsing helpers for CLI-mapped inputs.
|
|
338
|
-
* - Render config-derived defaults in dynamic option help text.
|
|
339
|
-
* - Access aws plugin ctx state via runtime narrowing (no casts).
|
|
340
|
-
*/
|
|
341
|
-
const silentLogger = {
|
|
342
|
-
debug: () => {
|
|
343
|
-
// no-op
|
|
344
|
-
},
|
|
345
|
-
info: () => {
|
|
346
|
-
// no-op
|
|
347
|
-
},
|
|
348
|
-
warn: () => {
|
|
349
|
-
// no-op
|
|
350
|
-
},
|
|
351
|
-
error: () => {
|
|
352
|
-
// no-op
|
|
353
|
-
},
|
|
354
|
-
};
|
|
355
|
-
const requireString = (v, msg) => {
|
|
356
|
-
if (typeof v !== 'string' || !v)
|
|
357
|
-
throw new Error(msg);
|
|
358
|
-
return v;
|
|
359
|
-
};
|
|
360
|
-
const toNumber = (v) => {
|
|
361
|
-
if (typeof v === 'undefined')
|
|
362
|
-
return;
|
|
363
|
-
if (typeof v === 'number')
|
|
364
|
-
return v;
|
|
365
|
-
if (typeof v === 'string' && v.trim())
|
|
366
|
-
return Number(v);
|
|
367
|
-
return;
|
|
368
|
-
};
|
|
369
|
-
const assertBytesWithinSecretsManagerLimit = (value) => {
|
|
370
|
-
const s = JSON.stringify(value);
|
|
371
|
-
const bytes = Buffer.byteLength(s, 'utf8');
|
|
372
|
-
if (bytes > 65_536) {
|
|
373
|
-
throw new Error(`SecretString size ${String(bytes)} bytes exceeds 65536 bytes; narrow selection with --from/--include/--exclude.`);
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
const describeDefault = (v) => {
|
|
377
|
-
if (Array.isArray(v))
|
|
378
|
-
return v.length ? v.join(' ') : 'none';
|
|
379
|
-
if (typeof v === 'string' && v.trim())
|
|
380
|
-
return v;
|
|
381
|
-
return 'none';
|
|
382
|
-
};
|
|
383
|
-
const isRecord = (v) => typeof v === 'object' && v !== null;
|
|
384
|
-
const getAwsRegion = (ctx) => {
|
|
385
|
-
if (!isRecord(ctx.plugins))
|
|
386
|
-
return;
|
|
387
|
-
const aws = ctx.plugins['aws'];
|
|
388
|
-
if (!isRecord(aws))
|
|
389
|
-
return;
|
|
390
|
-
const region = aws['region'];
|
|
391
|
-
return typeof region === 'string' ? region : undefined;
|
|
392
|
-
};
|
|
393
|
-
const describeConfigKeyListDefaults = ({ cfgInclude, cfgExclude, }) => {
|
|
394
|
-
// Avoid throwing in help rendering: show an explicit invalid marker.
|
|
395
|
-
if (cfgInclude?.length && cfgExclude?.length) {
|
|
396
|
-
const msg = '(invalid: both set in config)';
|
|
397
|
-
return { includeDefault: msg, excludeDefault: msg };
|
|
398
|
-
}
|
|
399
|
-
return {
|
|
400
|
-
includeDefault: describeDefault(cfgExclude?.length ? undefined : cfgInclude),
|
|
401
|
-
excludeDefault: describeDefault(cfgInclude?.length ? undefined : cfgExclude),
|
|
402
|
-
};
|
|
403
|
-
};
|
|
404
|
-
|
|
405
253
|
/**
|
|
406
254
|
* Requirements addressed:
|
|
407
255
|
* - Provide `aws secrets delete`.
|
|
@@ -431,14 +279,14 @@ const registerDeleteCommand = ({ cli, plugin, }) => {
|
|
|
431
279
|
const logger = console;
|
|
432
280
|
const ctx = cli.getCtx();
|
|
433
281
|
const cfg = plugin.readConfig(del);
|
|
434
|
-
const envRef =
|
|
282
|
+
const envRef = buildSpawnEnv(process.env, ctx.dotenv);
|
|
435
283
|
const secretNameRaw = opts.secretName ?? cfg.secretName ?? '$STACK_NAME';
|
|
436
|
-
const secretId =
|
|
284
|
+
const secretId = dotenvExpand(secretNameRaw, envRef);
|
|
437
285
|
if (!secretId)
|
|
438
286
|
throw new Error('secret-name is required.');
|
|
439
287
|
const recoveryWindowInDays = toNumber(opts.recoveryWindowDays);
|
|
440
288
|
const region = getAwsRegion(ctx);
|
|
441
|
-
const tools =
|
|
289
|
+
const tools = new AwsSecretsManagerTools({
|
|
442
290
|
clientConfig: region
|
|
443
291
|
? { region, logger: sdkLogger }
|
|
444
292
|
: { logger: sdkLogger },
|
|
@@ -715,13 +563,13 @@ const registerPullCommand = ({ cli, plugin, }) => {
|
|
|
715
563
|
const privateToken = rootOpts.privateToken ?? 'local';
|
|
716
564
|
const toRaw = opts.to ?? cfg.pull?.to ?? 'env:private';
|
|
717
565
|
const to = parseToSelector(toRaw);
|
|
718
|
-
const envRef =
|
|
566
|
+
const envRef = buildSpawnEnv(process.env, ctx.dotenv);
|
|
719
567
|
const secretNameRaw = opts.secretName ?? cfg.secretName ?? '$STACK_NAME';
|
|
720
|
-
const secretId =
|
|
568
|
+
const secretId = dotenvExpand(secretNameRaw, envRef);
|
|
721
569
|
if (!secretId)
|
|
722
570
|
throw new Error('secret-name is required.');
|
|
723
|
-
const region = getAwsRegion(ctx);
|
|
724
|
-
const tools =
|
|
571
|
+
const region = getAwsRegion$1(ctx);
|
|
572
|
+
const tools = new AwsSecretsManagerTools({
|
|
725
573
|
clientConfig: region
|
|
726
574
|
? { region, logger: sdkLogger }
|
|
727
575
|
: { logger: sdkLogger },
|
|
@@ -747,7 +595,7 @@ const registerPullCommand = ({ cli, plugin, }) => {
|
|
|
747
595
|
? await editDotenvFile(secrets, {
|
|
748
596
|
...editCommon,
|
|
749
597
|
scope: 'env',
|
|
750
|
-
env: requireString(bag.env
|
|
598
|
+
env: requireString(bag.env, 'env is required (use --env or defaultEnv).'),
|
|
751
599
|
})
|
|
752
600
|
: await editDotenvFile(secrets, {
|
|
753
601
|
...editCommon,
|
|
@@ -820,16 +668,16 @@ const registerPushCommand = ({ cli, plugin, }) => {
|
|
|
820
668
|
cfgInclude: cfg.push?.include,
|
|
821
669
|
cfgExclude: cfg.push?.exclude,
|
|
822
670
|
});
|
|
823
|
-
const envRef =
|
|
671
|
+
const envRef = buildSpawnEnv(process.env, ctx.dotenv);
|
|
824
672
|
const secretNameRaw = opts.secretName ?? cfg.secretName ?? '$STACK_NAME';
|
|
825
|
-
const secretId =
|
|
673
|
+
const secretId = dotenvExpand(secretNameRaw, envRef);
|
|
826
674
|
if (!secretId)
|
|
827
675
|
throw new Error('secret-name is required.');
|
|
828
676
|
const selected = selectEnvByProvenance(ctx.dotenv, ctx.dotenvProvenance, fromSelectors);
|
|
829
677
|
const secrets = applyIncludeExclude(selected, { include, exclude });
|
|
830
|
-
|
|
831
|
-
const region = getAwsRegion(ctx);
|
|
832
|
-
const tools =
|
|
678
|
+
assertByteLimit(secrets, 65_536, (v, l) => `SecretString size ${String(v)} bytes exceeds ${String(l)} bytes; narrow selection with --from/--include/--exclude.`);
|
|
679
|
+
const region = getAwsRegion$1(ctx);
|
|
680
|
+
const tools = new AwsSecretsManagerTools({
|
|
833
681
|
clientConfig: region
|
|
834
682
|
? { region, logger: sdkLogger }
|
|
835
683
|
: { logger: sdkLogger },
|
package/package.json
CHANGED
|
@@ -13,12 +13,9 @@
|
|
|
13
13
|
"url": "https://github.com/karmaniverous/aws-secrets-manager-tools/issues"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@aws-sdk/client-secrets-manager": "^3.
|
|
17
|
-
"@karmaniverous/
|
|
18
|
-
"
|
|
19
|
-
},
|
|
20
|
-
"peerDependencies": {
|
|
21
|
-
"aws-xray-sdk": "^3.12.0"
|
|
16
|
+
"@aws-sdk/client-secrets-manager": "^3.965.0",
|
|
17
|
+
"@karmaniverous/aws-xray-tools": "^0.2.0",
|
|
18
|
+
"@karmaniverous/get-dotenv": "^7.0.5"
|
|
22
19
|
},
|
|
23
20
|
"peerDependenciesMeta": {
|
|
24
21
|
"aws-xray-sdk": {
|
|
@@ -36,7 +33,7 @@
|
|
|
36
33
|
"@types/fs-extra": "^11.0.4",
|
|
37
34
|
"@types/node": "^25.0.3",
|
|
38
35
|
"@vitest/coverage-v8": "^4.0.16",
|
|
39
|
-
"@vitest/eslint-plugin": "^1.6.
|
|
36
|
+
"@vitest/eslint-plugin": "^1.6.6",
|
|
40
37
|
"auto-changelog": "^2.5.0",
|
|
41
38
|
"aws-xray-sdk": "^3.12.0",
|
|
42
39
|
"eslint": "^9.39.2",
|
|
@@ -45,13 +42,13 @@
|
|
|
45
42
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
46
43
|
"eslint-plugin-tsdoc": "^0.5.0",
|
|
47
44
|
"fs-extra": "^11.3.3",
|
|
48
|
-
"happy-dom": "^20.0
|
|
49
|
-
"knip": "^5.
|
|
45
|
+
"happy-dom": "^20.1.0",
|
|
46
|
+
"knip": "^5.80.0",
|
|
50
47
|
"lefthook": "^2.0.13",
|
|
51
48
|
"prettier": "^3.7.4",
|
|
52
|
-
"release-it": "^19.2.
|
|
49
|
+
"release-it": "^19.2.3",
|
|
53
50
|
"rimraf": "^6.1.2",
|
|
54
|
-
"rollup": "^4.
|
|
51
|
+
"rollup": "^4.55.1",
|
|
55
52
|
"rollup-plugin-dts": "^6.3.0",
|
|
56
53
|
"tslib": "^2.8.1",
|
|
57
54
|
"tsx": "^4.21.0",
|
|
@@ -59,7 +56,7 @@
|
|
|
59
56
|
"typedoc-plugin-mdn-links": "^5.0.10",
|
|
60
57
|
"typedoc-plugin-replace-text": "^4.2.0",
|
|
61
58
|
"typescript": "^5.9.3",
|
|
62
|
-
"typescript-eslint": "^8.
|
|
59
|
+
"typescript-eslint": "^8.52.0",
|
|
63
60
|
"vitest": "^4.0.16"
|
|
64
61
|
},
|
|
65
62
|
"engines": {
|
|
@@ -108,15 +105,15 @@
|
|
|
108
105
|
"npm run knip",
|
|
109
106
|
"npm run build"
|
|
110
107
|
],
|
|
111
|
-
"before:npm:release": [
|
|
112
|
-
"npx auto-changelog -p",
|
|
113
|
-
"npm run docs",
|
|
114
|
-
"git add -A"
|
|
115
|
-
],
|
|
116
108
|
"after:release": [
|
|
117
109
|
"git switch -c release/${version}",
|
|
118
110
|
"git push -u origin release/${version}",
|
|
119
111
|
"git switch ${branchName}"
|
|
112
|
+
],
|
|
113
|
+
"after:bump": [
|
|
114
|
+
"npx auto-changelog -p",
|
|
115
|
+
"npm run docs",
|
|
116
|
+
"git add CHANGELOG.md"
|
|
120
117
|
]
|
|
121
118
|
},
|
|
122
119
|
"npm": {
|
|
@@ -144,5 +141,5 @@
|
|
|
144
141
|
},
|
|
145
142
|
"type": "module",
|
|
146
143
|
"types": "dist/index.d.ts",
|
|
147
|
-
"version": "0.
|
|
144
|
+
"version": "0.2.1"
|
|
148
145
|
}
|