env-secrets 0.5.3 → 0.5.4
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/.claude/rules/cicd.md +189 -0
- package/.claude/rules/docs.md +96 -0
- package/.claude/rules/git-hooks.md +43 -0
- package/.claude/rules/local-dev-badges.md +91 -0
- package/.claude/rules/local-dev-env.md +382 -0
- package/.claude/rules/local-dev-license.md +104 -0
- package/.claude/rules/local-dev-mcp.md +70 -0
- package/.claude/rules/observability.md +23 -0
- package/.claude/rules/publishing-api.md +158 -0
- package/.claude/rules/publishing-apps.md +204 -0
- package/.claude/rules/publishing-apt.md +146 -0
- package/.claude/rules/publishing-brew.md +110 -0
- package/.claude/rules/publishing-cli.md +238 -0
- package/.claude/rules/publishing-libraries.md +115 -0
- package/.claude/rules/publishing-sdks.md +109 -0
- package/.claude/rules/publishing-web.md +185 -0
- package/.claude/rules/typescript-linting.md +141 -0
- package/.claude/rules/typescript-logging.md +356 -0
- package/.claude/rules/typescript-testing.md +185 -0
- package/.claude/settings.json +18 -0
- package/.claude/skills/github-health-check.skill +0 -0
- package/.codex/rules/cicd.md +21 -0
- package/.codex/rules/docs.md +98 -0
- package/.codex/rules/git-hooks.md +43 -0
- package/.codex/rules/github-health-check.md +440 -0
- package/.codex/rules/local-dev-env.md +47 -0
- package/.codex/rules/local-dev-license.md +3 -1
- package/.codex/rules/publishing-api.md +160 -0
- package/.codex/rules/publishing-apps.md +206 -0
- package/.codex/rules/publishing-apt.md +148 -0
- package/.codex/rules/publishing-brew.md +112 -0
- package/.codex/rules/publishing-cli.md +240 -0
- package/.codex/rules/publishing-libraries.md +117 -0
- package/.codex/rules/publishing-sdks.md +111 -0
- package/.codex/rules/publishing-web.md +187 -0
- package/.codex/rules/typescript-linting.md +143 -0
- package/.codex/rules/typescript-logging.md +358 -0
- package/.codex/rules/typescript-testing.md +187 -0
- package/.github/workflows/deploy-docs.yml +1 -1
- package/.github/workflows/unittests.yaml +1 -1
- package/.rulesrc.json +20 -0
- package/AGENTS.md +34 -0
- package/CLAUDE.md +58 -0
- package/README.md +17 -3
- package/__e2e__/aws-secret-value-args.test.ts +142 -0
- package/__tests__/cli/helpers.test.ts +35 -0
- package/dist/cli/helpers.js +13 -1
- package/dist/index.js +79 -40
- package/docs/AWS.md +42 -13
- package/package.json +5 -5
- package/src/cli/helpers.ts +16 -0
- package/src/index.ts +97 -48
package/docs/AWS.md
CHANGED
|
@@ -153,11 +153,12 @@ In addition to injecting variables into a process, `env-secrets` can manage AWS
|
|
|
153
153
|
|
|
154
154
|
- `env-secrets aws secret create`
|
|
155
155
|
- `env-secrets aws secret update`
|
|
156
|
+
- `env-secrets aws secret upsert` (alias: `import`)
|
|
156
157
|
- `env-secrets aws secret append`
|
|
157
158
|
- `env-secrets aws secret remove`
|
|
158
|
-
- `env-secrets aws secret upsert` (alias: `import`)
|
|
159
159
|
- `env-secrets aws secret list`
|
|
160
160
|
- `env-secrets aws secret get`
|
|
161
|
+
- `env-secrets aws secret value`
|
|
161
162
|
- `env-secrets aws secret delete`
|
|
162
163
|
|
|
163
164
|
`aws secret` subcommands consistently honor `--region`, `--profile`, and `--output`.
|
|
@@ -165,8 +166,8 @@ Use these options directly with each subcommand.
|
|
|
165
166
|
|
|
166
167
|
### `aws -s` vs `aws secret ...`
|
|
167
168
|
|
|
168
|
-
- `env-secrets aws -s <secret-name> -- <command>`: retrieves a secret value and injects it into the environment for the spawned process (or use `-o <file>` to write exports to a file).
|
|
169
|
-
- `env-secrets aws secret ...`: management commands only (`create`, `update`, `
|
|
169
|
+
- `env-secrets aws -s <secret-name> -- <command>`: retrieves a secret value and injects it into the environment for the spawned process (or use `-o <file>` to write exports to a file). Use `--no-shell` to run the program directly without a shell wrapper (disables shell expansion).
|
|
170
|
+
- `env-secrets aws secret ...`: management commands only (`create`, `update`, `upsert/import`, `append`, `remove`, `list`, `get`, `value`, `delete`).
|
|
170
171
|
|
|
171
172
|
Example:
|
|
172
173
|
|
|
@@ -206,18 +207,24 @@ source secrets.env
|
|
|
206
207
|
- dotenv-style input is converted (`KEY=value` -> `{"KEY":"value"}`)
|
|
207
208
|
- non-object/scalar input is wrapped (`super-secret-value` -> `{"value":"super-secret-value"}`)
|
|
208
209
|
|
|
210
|
+
Optional flags: `-d/--description`, `-k/--kms-key-id`, `-t/--tag key=value` (repeatable).
|
|
211
|
+
|
|
209
212
|
2. **Create from stdin (recommended for sensitive values):**
|
|
210
213
|
|
|
211
214
|
```bash
|
|
212
215
|
echo -n 'super-secret-value' | env-secrets aws secret create -n my-app/dev/raw --value-stdin -r us-east-1
|
|
213
216
|
```
|
|
214
217
|
|
|
218
|
+
`create` and `update` accept `--value`, `--value-stdin`, or `--file` — use only one.
|
|
219
|
+
|
|
215
220
|
3. **Update an existing secret value:**
|
|
216
221
|
|
|
217
222
|
```bash
|
|
218
223
|
env-secrets aws secret update -n my-app/dev/api -v '{"API_KEY":"rotated"}' -r us-east-1
|
|
219
224
|
```
|
|
220
225
|
|
|
226
|
+
Optional flags: `-d/--description`, `-k/--kms-key-id`. Accepts `--value-stdin` or `--file` instead of `-v`.
|
|
227
|
+
|
|
221
228
|
4. **Upsert from an env file into one JSON secret (`export KEY=value` or `KEY=value`):**
|
|
222
229
|
|
|
223
230
|
```bash
|
|
@@ -232,47 +239,69 @@ source secrets.env
|
|
|
232
239
|
{ "API_KEY": "abc123", "DATABASE_URL": "postgres://..." }
|
|
233
240
|
```
|
|
234
241
|
|
|
242
|
+
Optional flags: `-d/--description`, `-k/--kms-key-id`, `-t/--tag key=value` (repeatable, applies on create only).
|
|
243
|
+
|
|
235
244
|
5. **Append/remove keys in an existing JSON secret:**
|
|
236
245
|
|
|
237
246
|
```bash
|
|
247
|
+
# Append a key (accepts --value-stdin or --file instead of -v)
|
|
238
248
|
env-secrets aws secret append -n my-app/dev --key JIRA_EMAIL_TOKEN -v blah -r us-east-1
|
|
239
|
-
|
|
249
|
+
|
|
250
|
+
# Remove one or more keys
|
|
251
|
+
env-secrets aws secret remove -n my-app/dev --key OLD_TOKEN --key UNUSED_KEY -r us-east-1
|
|
240
252
|
```
|
|
241
253
|
|
|
242
|
-
6. **List secrets by prefix:**
|
|
254
|
+
6. **List secrets by prefix or tag:**
|
|
243
255
|
|
|
244
256
|
```bash
|
|
245
257
|
env-secrets aws secret list --prefix my-app/dev -r us-east-1 --output table
|
|
246
|
-
```
|
|
247
258
|
|
|
248
|
-
|
|
259
|
+
# Filter by tag
|
|
260
|
+
env-secrets aws secret list -t env=production -t team=platform -r us-east-1
|
|
249
261
|
|
|
250
|
-
|
|
262
|
+
# Multi-region comparison
|
|
251
263
|
env-secrets aws secret list --prefix my-app/dev -r us-west-2 --output json
|
|
252
264
|
env-secrets aws secret list --prefix my-app/dev -r us-east-1 --output json
|
|
253
265
|
```
|
|
254
266
|
|
|
255
|
-
7. **Get metadata and version info (without printing secret
|
|
267
|
+
7. **Get metadata and version info (without printing secret values):**
|
|
256
268
|
|
|
257
269
|
```bash
|
|
258
270
|
env-secrets aws secret get -n my-app/dev/api -r us-east-1 --output json
|
|
259
271
|
```
|
|
260
272
|
|
|
261
|
-
8. **
|
|
273
|
+
8. **Get the values of a secret:**
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Table output — values masked by default
|
|
277
|
+
env-secrets aws secret value -n my-app/dev/api -r us-east-1
|
|
278
|
+
|
|
279
|
+
# Reveal actual values (warning printed to stderr)
|
|
280
|
+
env-secrets aws secret value -n my-app/dev/api -r us-east-1 --reveal
|
|
281
|
+
|
|
282
|
+
# JSON output — always returns full values (warns on TTY)
|
|
283
|
+
env-secrets aws secret value -n my-app/dev/api -r us-east-1 --output json
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
9. **Delete with explicit confirmation:**
|
|
262
287
|
|
|
263
288
|
```bash
|
|
289
|
+
# With a recovery window (7–30 days)
|
|
264
290
|
env-secrets aws secret delete -n my-app/dev/raw --recovery-days 7 --yes -r us-east-1
|
|
291
|
+
|
|
292
|
+
# Permanent delete with no recovery window
|
|
293
|
+
env-secrets aws secret delete -n my-app/dev/raw --force-delete-without-recovery --yes -r us-east-1
|
|
265
294
|
```
|
|
266
295
|
|
|
267
296
|
### Secret Management Safety Notes
|
|
268
297
|
|
|
269
|
-
- `delete` requires `--yes`.
|
|
270
|
-
- `create
|
|
298
|
+
- `delete` requires `--yes`. Use either `--recovery-days <7-30>` or `--force-delete-without-recovery`, not both.
|
|
299
|
+
- `create`, `update`, and `append` accept `--value`, `--value-stdin`, or `--file` (use only one).
|
|
271
300
|
- `create` always stores `SecretString` as a JSON object.
|
|
272
301
|
- `append` and `remove` require the secret value to be a JSON object.
|
|
273
302
|
- `upsert/import --file --name` parses `export KEY=value` and `KEY=value`, stores them as one JSON secret object, ignores blank lines/comments, and reports `created`, `updated`, `skipped`, and `failed`.
|
|
274
303
|
- Use `--value-stdin` to avoid shell history leakage for sensitive values.
|
|
275
|
-
-
|
|
304
|
+
- `value` masks secret values as `****` in table output by default. Use `--reveal` to show them (prints a warning to stderr). JSON output always returns full values and warns when stdout is a terminal.
|
|
276
305
|
|
|
277
306
|
## Examples
|
|
278
307
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "env-secrets",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "get secrets from a secrets vault and inject them into the running environment",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Mark C Allen (@markcallen)",
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
"release-it": "^15.11.0",
|
|
49
49
|
"rimraf": "^3.0.2",
|
|
50
50
|
"tsc-files": "^1.1.4",
|
|
51
|
-
"ts-jest": "^29.4.
|
|
51
|
+
"ts-jest": "^29.4.9",
|
|
52
52
|
"ts-node": "^10.9.2",
|
|
53
53
|
"typescript": "^4.9.5"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@aws-sdk/client-secrets-manager": "^3.
|
|
57
|
-
"@aws-sdk/client-sts": "^3.
|
|
58
|
-
"@aws-sdk/credential-providers": "^3.
|
|
56
|
+
"@aws-sdk/client-secrets-manager": "^3.1038.0",
|
|
57
|
+
"@aws-sdk/client-sts": "^3.1038.0",
|
|
58
|
+
"@aws-sdk/credential-providers": "^3.1038.0",
|
|
59
59
|
"commander": "^9.5.0",
|
|
60
60
|
"debug": "^4.4.3"
|
|
61
61
|
},
|
package/src/cli/helpers.ts
CHANGED
|
@@ -218,6 +218,22 @@ export const parseEnvSecretsFile = async (
|
|
|
218
218
|
return parseEnvSecrets(content);
|
|
219
219
|
};
|
|
220
220
|
|
|
221
|
+
export const resolveOutputFormat = (
|
|
222
|
+
options: { output?: string },
|
|
223
|
+
command?: CommandLikeWithGlobalOpts
|
|
224
|
+
): OutputFormat => {
|
|
225
|
+
if (options.output) {
|
|
226
|
+
return asOutputFormat(options.output);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const globalOutput = command?.optsWithGlobals?.()?.output;
|
|
230
|
+
if (globalOutput === 'json' || globalOutput === 'table') {
|
|
231
|
+
return globalOutput;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return 'table';
|
|
235
|
+
};
|
|
236
|
+
|
|
221
237
|
export const resolveAwsScope = (
|
|
222
238
|
options: AwsScopeOptions,
|
|
223
239
|
command?: CommandLikeWithGlobalOpts
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
printData,
|
|
24
24
|
parseRecoveryDays,
|
|
25
25
|
resolveAwsScope,
|
|
26
|
+
resolveOutputFormat,
|
|
26
27
|
resolveSecretValue
|
|
27
28
|
} from './cli/helpers';
|
|
28
29
|
import { objectToExport } from './vaults/utils';
|
|
@@ -208,12 +209,7 @@ secretCommand
|
|
|
208
209
|
.action(async (options, command) => {
|
|
209
210
|
try {
|
|
210
211
|
const { profile, region } = resolveAwsScope(options, command);
|
|
211
|
-
const
|
|
212
|
-
const output =
|
|
213
|
-
options.output ??
|
|
214
|
-
(typeof globalOptions.output === 'string'
|
|
215
|
-
? globalOptions.output
|
|
216
|
-
: 'table');
|
|
212
|
+
const output = resolveOutputFormat(options, command);
|
|
217
213
|
const value = await resolveSecretValue(
|
|
218
214
|
options.value,
|
|
219
215
|
options.valueStdin,
|
|
@@ -265,12 +261,7 @@ secretCommand
|
|
|
265
261
|
.action(async (options, command) => {
|
|
266
262
|
try {
|
|
267
263
|
const { profile, region } = resolveAwsScope(options, command);
|
|
268
|
-
const
|
|
269
|
-
const output =
|
|
270
|
-
options.output ??
|
|
271
|
-
(typeof globalOptions.output === 'string'
|
|
272
|
-
? globalOptions.output
|
|
273
|
-
: 'table');
|
|
264
|
+
const output = resolveOutputFormat(options, command);
|
|
274
265
|
const value = await resolveSecretValue(
|
|
275
266
|
options.value,
|
|
276
267
|
options.valueStdin,
|
|
@@ -320,12 +311,7 @@ secretCommand
|
|
|
320
311
|
.action(async (options, command) => {
|
|
321
312
|
try {
|
|
322
313
|
const { profile, region } = resolveAwsScope(options, command);
|
|
323
|
-
const
|
|
324
|
-
const output =
|
|
325
|
-
options.output ??
|
|
326
|
-
(typeof globalOptions.output === 'string'
|
|
327
|
-
? globalOptions.output
|
|
328
|
-
: 'table');
|
|
314
|
+
const output = resolveOutputFormat(options, command);
|
|
329
315
|
const parsed = await parseEnvSecretsFile(options.file);
|
|
330
316
|
|
|
331
317
|
if (parsed.entries.length === 0) {
|
|
@@ -454,12 +440,7 @@ secretCommand
|
|
|
454
440
|
.action(async (options, command) => {
|
|
455
441
|
try {
|
|
456
442
|
const { profile, region } = resolveAwsScope(options, command);
|
|
457
|
-
const
|
|
458
|
-
const output =
|
|
459
|
-
options.output ??
|
|
460
|
-
(typeof globalOptions.output === 'string'
|
|
461
|
-
? globalOptions.output
|
|
462
|
-
: 'table');
|
|
443
|
+
const output = resolveOutputFormat(options, command);
|
|
463
444
|
const value = await resolveSecretValue(
|
|
464
445
|
options.value,
|
|
465
446
|
options.valueStdin,
|
|
@@ -519,12 +500,7 @@ secretCommand
|
|
|
519
500
|
.action(async (options, command) => {
|
|
520
501
|
try {
|
|
521
502
|
const { profile, region } = resolveAwsScope(options, command);
|
|
522
|
-
const
|
|
523
|
-
const output =
|
|
524
|
-
options.output ??
|
|
525
|
-
(typeof globalOptions.output === 'string'
|
|
526
|
-
? globalOptions.output
|
|
527
|
-
: 'table');
|
|
503
|
+
const output = resolveOutputFormat(options, command);
|
|
528
504
|
const keys = options.key as string[];
|
|
529
505
|
const current = await getSecretString({
|
|
530
506
|
name: options.name,
|
|
@@ -590,12 +566,7 @@ secretCommand
|
|
|
590
566
|
.action(async (options, command) => {
|
|
591
567
|
try {
|
|
592
568
|
const { profile, region } = resolveAwsScope(options, command);
|
|
593
|
-
const
|
|
594
|
-
const output =
|
|
595
|
-
options.output ??
|
|
596
|
-
(typeof globalOptions.output === 'string'
|
|
597
|
-
? globalOptions.output
|
|
598
|
-
: 'table');
|
|
569
|
+
const output = resolveOutputFormat(options, command);
|
|
599
570
|
const result = await listSecrets({
|
|
600
571
|
prefix: options.prefix,
|
|
601
572
|
tags: options.tag,
|
|
@@ -632,12 +603,7 @@ secretCommand
|
|
|
632
603
|
.action(async (options, command) => {
|
|
633
604
|
try {
|
|
634
605
|
const { profile, region } = resolveAwsScope(options, command);
|
|
635
|
-
const
|
|
636
|
-
const output =
|
|
637
|
-
options.output ??
|
|
638
|
-
(typeof globalOptions.output === 'string'
|
|
639
|
-
? globalOptions.output
|
|
640
|
-
: 'table');
|
|
606
|
+
const output = resolveOutputFormat(options, command);
|
|
641
607
|
const result = await getSecretMetadata({
|
|
642
608
|
name: options.name,
|
|
643
609
|
profile,
|
|
@@ -672,6 +638,94 @@ secretCommand
|
|
|
672
638
|
}
|
|
673
639
|
});
|
|
674
640
|
|
|
641
|
+
secretCommand
|
|
642
|
+
.command('value')
|
|
643
|
+
.description('get the values of a secret')
|
|
644
|
+
.requiredOption('-n, --name <name>', 'secret name')
|
|
645
|
+
.option(
|
|
646
|
+
'--reveal',
|
|
647
|
+
'reveal secret values in table output (values are masked by default)',
|
|
648
|
+
false
|
|
649
|
+
)
|
|
650
|
+
.option('-p, --profile <profile>', 'profile to use')
|
|
651
|
+
.option('-r, --region <region>', 'region to use')
|
|
652
|
+
.option('--output <format>', 'output format: json|table')
|
|
653
|
+
.action(async (options, command) => {
|
|
654
|
+
try {
|
|
655
|
+
const { profile, region } = resolveAwsScope(options, command);
|
|
656
|
+
const output = resolveOutputFormat(options, command);
|
|
657
|
+
|
|
658
|
+
let secretString: string;
|
|
659
|
+
try {
|
|
660
|
+
secretString = await getSecretString({
|
|
661
|
+
name: options.name,
|
|
662
|
+
profile,
|
|
663
|
+
region
|
|
664
|
+
});
|
|
665
|
+
} catch (error: unknown) {
|
|
666
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
667
|
+
if (msg.includes('cannot be edited with append/remove')) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
`Secret "${options.name}" is stored as binary and cannot be displayed as text.`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
throw error;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
let jsonEntries: Array<{ key: string; value: unknown }>;
|
|
676
|
+
try {
|
|
677
|
+
const parsed = JSON.parse(secretString) as unknown;
|
|
678
|
+
if (parsed && !Array.isArray(parsed) && typeof parsed === 'object') {
|
|
679
|
+
jsonEntries = Object.entries(parsed as Record<string, unknown>).map(
|
|
680
|
+
([key, value]) => ({ key, value })
|
|
681
|
+
);
|
|
682
|
+
} else {
|
|
683
|
+
jsonEntries = [{ key: options.name, value: secretString }];
|
|
684
|
+
}
|
|
685
|
+
} catch {
|
|
686
|
+
jsonEntries = [{ key: options.name, value: secretString }];
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (output === 'json') {
|
|
690
|
+
if (process.stdout.isTTY) {
|
|
691
|
+
// eslint-disable-next-line no-console
|
|
692
|
+
console.error('Warning: displaying sensitive secret values.');
|
|
693
|
+
}
|
|
694
|
+
const result = Object.fromEntries(
|
|
695
|
+
jsonEntries.map(({ key, value }) => [key, value])
|
|
696
|
+
);
|
|
697
|
+
// eslint-disable-next-line no-console
|
|
698
|
+
console.log(JSON.stringify(result, null, 2));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (options.reveal) {
|
|
703
|
+
// eslint-disable-next-line no-console
|
|
704
|
+
console.error('Warning: displaying sensitive secret values.');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const rows = jsonEntries.map(({ key, value }) => ({
|
|
708
|
+
key,
|
|
709
|
+
value: options.reveal
|
|
710
|
+
? typeof value === 'string'
|
|
711
|
+
? value
|
|
712
|
+
: JSON.stringify(value)
|
|
713
|
+
: '****'
|
|
714
|
+
}));
|
|
715
|
+
|
|
716
|
+
printData(
|
|
717
|
+
asOutputFormat(output),
|
|
718
|
+
[
|
|
719
|
+
{ key: 'key', label: 'Key' },
|
|
720
|
+
{ key: 'value', label: 'Value' }
|
|
721
|
+
],
|
|
722
|
+
rows
|
|
723
|
+
);
|
|
724
|
+
} catch (error: unknown) {
|
|
725
|
+
exitWithError(error);
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
|
|
675
729
|
secretCommand
|
|
676
730
|
.command('delete')
|
|
677
731
|
.description('delete a secret in AWS Secrets Manager')
|
|
@@ -693,12 +747,7 @@ secretCommand
|
|
|
693
747
|
.action(async (options, command) => {
|
|
694
748
|
try {
|
|
695
749
|
const { profile, region } = resolveAwsScope(options, command);
|
|
696
|
-
const
|
|
697
|
-
const output =
|
|
698
|
-
options.output ??
|
|
699
|
-
(typeof globalOptions.output === 'string'
|
|
700
|
-
? globalOptions.output
|
|
701
|
-
: 'table');
|
|
750
|
+
const output = resolveOutputFormat(options, command);
|
|
702
751
|
if (!options.yes) {
|
|
703
752
|
throw new Error('Delete requires --yes confirmation.');
|
|
704
753
|
}
|