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/.rulesrc.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": ["claude", "codex"],
|
|
3
|
+
"agents": [
|
|
4
|
+
"local-dev",
|
|
5
|
+
"docs",
|
|
6
|
+
"cicd",
|
|
7
|
+
"observability",
|
|
8
|
+
"publishing",
|
|
9
|
+
"git-hooks",
|
|
10
|
+
"linting",
|
|
11
|
+
"logging",
|
|
12
|
+
"testing"
|
|
13
|
+
],
|
|
14
|
+
"skills": ["github-health-check"],
|
|
15
|
+
"ballastVersion": "5.9.2",
|
|
16
|
+
"languages": ["typescript"],
|
|
17
|
+
"paths": {
|
|
18
|
+
"typescript": ["."]
|
|
19
|
+
}
|
|
20
|
+
}
|
package/AGENTS.md
CHANGED
|
@@ -156,3 +156,37 @@ yarn install
|
|
|
156
156
|
brew install awscli-local
|
|
157
157
|
yarn build
|
|
158
158
|
```
|
|
159
|
+
|
|
160
|
+
## Installed agent rules
|
|
161
|
+
|
|
162
|
+
Created by [Ballast](https://github.com/everydaydevopsio/ballast) v5.9.2. Do not edit this section.
|
|
163
|
+
|
|
164
|
+
Read and follow these rule files in `.codex/rules/` when they apply:
|
|
165
|
+
|
|
166
|
+
- `.codex/rules/local-dev-badges.md` — Add standard badges (CI, Release, License, GitHub Release, npm) to the top of README.md
|
|
167
|
+
- `.codex/rules/local-dev-env.md` — Local development environment specialist - reproducible dev setup, DX, and documentation
|
|
168
|
+
- `.codex/rules/local-dev-license.md` — License setup - ensure LICENSE file, package.json license field, and README reference (default MIT; overridable in AGENTS.md/CLAUDE.md)
|
|
169
|
+
- `.codex/rules/local-dev-mcp.md` — Optional: use GitHub MCP and issues MCP (Jira/Linear/GitHub) for local-dev context
|
|
170
|
+
- `.codex/rules/docs.md` — Documentation specialist - GitHub Markdown docs by default, or maintain existing Docusaurus sites with publish-docs automation
|
|
171
|
+
- `.codex/rules/cicd.md` — CI/CD specialist - pipeline design, quality gates, and deployment
|
|
172
|
+
- `.codex/rules/observability.md` — Observability specialist - logging, tracing, metrics, and SLOs
|
|
173
|
+
- `.codex/rules/publishing-api.md` — REST API publishing specialist - Docker CD with Kubernetes health probes and Helm chart update
|
|
174
|
+
- `.codex/rules/publishing-apps.md` — App publishing specialist - npmjs for Node apps, PyPI for Python apps, GitHub Releases for Go apps
|
|
175
|
+
- `.codex/rules/publishing-apt.md` — APT/deb package publishing specialist - GoReleaser nfpms and GitHub Releases
|
|
176
|
+
- `.codex/rules/publishing-brew.md` — Homebrew tap publishing specialist - GoReleaser brews block and tap repo setup
|
|
177
|
+
- `.codex/rules/publishing-cli.md` — CLI publishing specialist - GoReleaser for Go, npmjs for Node, PyPI for Python
|
|
178
|
+
- `.codex/rules/publishing-libraries.md` — Library publishing specialist - npmjs for TypeScript, PyPI for Python, GitHub tags/releases for Go
|
|
179
|
+
- `.codex/rules/publishing-sdks.md` — SDK publishing specialist - npmjs for TypeScript SDKs, PyPI for Python SDKs, GitHub tags/releases for Go SDKs
|
|
180
|
+
- `.codex/rules/publishing-web.md` — Web app publishing specialist - Docker to GHCR/Docker Hub with Helm chart CD on push to main
|
|
181
|
+
- `.codex/rules/git-hooks.md` — Git hook specialist - configure pre-commit, pre-push, and Husky workflows that match the repository layout
|
|
182
|
+
- `.codex/rules/typescript-linting.md` — TypeScript linting specialist - implements comprehensive linting and code formatting for TypeScript/JavaScript projects
|
|
183
|
+
- `.codex/rules/typescript-logging.md` — Centralized logging specialist - configures Pino with Fluentd for Node/Next.js, and pino-browser to /api/logs
|
|
184
|
+
- `.codex/rules/typescript-testing.md` — Testing specialist - sets up Jest (default) or Vitest for Vite projects, 50% coverage, and test step in build GitHub Action
|
|
185
|
+
|
|
186
|
+
## Installed skills
|
|
187
|
+
|
|
188
|
+
Created by [Ballast](https://github.com/everydaydevopsio/ballast) v5.9.2. Do not edit this section.
|
|
189
|
+
|
|
190
|
+
Read and use these skill files in `.codex/rules/` when they are relevant:
|
|
191
|
+
|
|
192
|
+
- `.codex/rules/github-health-check.md` — Run a comprehensive GitHub repository health check. Use this skill whenever the user asks to: check GitHub health, audit the repo, check CI status, review open PRs, merge Dependabot PRs, check code coverage, check GitHub Code Quality, check GitHub security feature enablement, check security advisories, check Dependabot alerts, check code scanning alerts, check secret scanning alerts, check Snyk integration, keep GitHub in good shape, or any variation of "how is the repo doing". Also trigger for: "check dependabot PRs", "any PRs to merge", "check branch status", "repo health", "GitHub status check", "what needs attention in GitHub", "tidy up GitHub".
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code for working in this repository.
|
|
4
|
+
|
|
5
|
+
## Repository Facts
|
|
6
|
+
|
|
7
|
+
Use this section for durable repo-specific facts that agents repeatedly need. Prefer facts stored here over re-deriving them with shell commands on every task.
|
|
8
|
+
|
|
9
|
+
Keep only stable, reviewable metadata here. Do not store secrets, credentials, or ephemeral runtime state.
|
|
10
|
+
|
|
11
|
+
Suggested facts to record:
|
|
12
|
+
|
|
13
|
+
- Canonical GitHub repo: `<OWNER/REPO>`
|
|
14
|
+
- Default branch: `<main>`
|
|
15
|
+
- Primary package manager: `<pnpm | npm | yarn | uv | go>`
|
|
16
|
+
- Version-file locations agents should check first: `<.nvmrc, packageManager, pyproject.toml, go.mod, etc.>`
|
|
17
|
+
- Canonical config files: `<paths agents should read before falling back to discovery>`
|
|
18
|
+
- Primary CI workflows: `<workflow filenames>`
|
|
19
|
+
- Primary release/publish workflows: `<workflow filenames>`
|
|
20
|
+
- Preferred build/test/lint/format/coverage commands: `<commands>`
|
|
21
|
+
- Coverage threshold: `<value>`
|
|
22
|
+
- Generated or protected paths agents should avoid editing directly: `<paths>`
|
|
23
|
+
|
|
24
|
+
Update this section when those facts change. If live runtime state is required, discover it separately instead of treating it as a durable repo fact.
|
|
25
|
+
|
|
26
|
+
## Installed agent rules
|
|
27
|
+
|
|
28
|
+
Created by [Ballast](https://github.com/everydaydevopsio/ballast) v5.9.2. Do not edit this section.
|
|
29
|
+
|
|
30
|
+
Read and follow these rule files in `.claude/rules/` when they apply:
|
|
31
|
+
|
|
32
|
+
- `.claude/rules/local-dev-badges.md` — Add standard badges (CI, Release, License, GitHub Release, npm) to the top of README.md
|
|
33
|
+
- `.claude/rules/local-dev-env.md` — Local development environment specialist - reproducible dev setup, DX, and documentation
|
|
34
|
+
- `.claude/rules/local-dev-license.md` — License setup - ensure LICENSE file, package.json license field, and README reference (default MIT; overridable in AGENTS.md/CLAUDE.md)
|
|
35
|
+
- `.claude/rules/local-dev-mcp.md` — Optional: use GitHub MCP and issues MCP (Jira/Linear/GitHub) for local-dev context
|
|
36
|
+
- `.claude/rules/docs.md` — Documentation specialist - GitHub Markdown docs by default, or maintain existing Docusaurus sites with publish-docs automation
|
|
37
|
+
- `.claude/rules/cicd.md` — CI/CD specialist - pipeline design, quality gates, and deployment
|
|
38
|
+
- `.claude/rules/observability.md` — Observability specialist - logging, tracing, metrics, and SLOs
|
|
39
|
+
- `.claude/rules/publishing-api.md` — REST API publishing specialist - Docker CD with Kubernetes health probes and Helm chart update
|
|
40
|
+
- `.claude/rules/publishing-apps.md` — App publishing specialist - npmjs for Node apps, PyPI for Python apps, GitHub Releases for Go apps
|
|
41
|
+
- `.claude/rules/publishing-apt.md` — APT/deb package publishing specialist - GoReleaser nfpms and GitHub Releases
|
|
42
|
+
- `.claude/rules/publishing-brew.md` — Homebrew tap publishing specialist - GoReleaser brews block and tap repo setup
|
|
43
|
+
- `.claude/rules/publishing-cli.md` — CLI publishing specialist - GoReleaser for Go, npmjs for Node, PyPI for Python
|
|
44
|
+
- `.claude/rules/publishing-libraries.md` — Library publishing specialist - npmjs for TypeScript, PyPI for Python, GitHub tags/releases for Go
|
|
45
|
+
- `.claude/rules/publishing-sdks.md` — SDK publishing specialist - npmjs for TypeScript SDKs, PyPI for Python SDKs, GitHub tags/releases for Go SDKs
|
|
46
|
+
- `.claude/rules/publishing-web.md` — Web app publishing specialist - Docker to GHCR/Docker Hub with Helm chart CD on push to main
|
|
47
|
+
- `.claude/rules/git-hooks.md` — Git hook specialist - configure pre-commit, pre-push, and Husky workflows that match the repository layout
|
|
48
|
+
- `.claude/rules/typescript-linting.md` — TypeScript linting specialist - implements comprehensive linting and code formatting for TypeScript/JavaScript projects
|
|
49
|
+
- `.claude/rules/typescript-logging.md` — Centralized logging specialist - configures Pino with Fluentd for Node/Next.js, and pino-browser to /api/logs
|
|
50
|
+
- `.claude/rules/typescript-testing.md` — Testing specialist - sets up Jest (default) or Vitest for Vite projects, 50% coverage, and test step in build GitHub Action
|
|
51
|
+
|
|
52
|
+
## Installed skills
|
|
53
|
+
|
|
54
|
+
Created by [Ballast](https://github.com/everydaydevopsio/ballast) v5.9.2. Do not edit this section.
|
|
55
|
+
|
|
56
|
+
Read and use these skill files in `.claude/skills/` when they are relevant:
|
|
57
|
+
|
|
58
|
+
- `.claude/skills/github-health-check.skill` — Run a comprehensive GitHub repository health check. Use this skill whenever the user asks to: check GitHub health, audit the repo, check CI status, review open PRs, merge Dependabot PRs, check code coverage, check GitHub Code Quality, check GitHub security feature enablement, check security advisories, check Dependabot alerts, check code scanning alerts, check secret scanning alerts, check Snyk integration, keep GitHub in good shape, or any variation of "how is the repo doing". Also trigger for: "check dependabot PRs", "any PRs to merge", "check branch status", "repo health", "GitHub status check", "what needs attention in GitHub", "tidy up GitHub".
|
package/README.md
CHANGED
|
@@ -101,9 +101,10 @@ env-secrets aws -s my-app-secrets -r us-east-1 -- node app.js
|
|
|
101
101
|
- `-r, --region <region>` (optional): AWS region where the secret is stored. If not provided, uses `AWS_DEFAULT_REGION` environment variable
|
|
102
102
|
- `-p, --profile <profile>` (optional): Local AWS profile to use. If not provided, uses `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables
|
|
103
103
|
- `-o, --output <file>` (optional): Output secrets to a file instead of injecting into environment variables. File will be created with 0400 permissions and will not overwrite existing files
|
|
104
|
+
- `--no-shell` (optional): Run the program directly without a shell wrapper. Disables shell expansion — use when you do not need `$VAR` interpolation in arguments
|
|
104
105
|
- `-- <program-to-run>`: The program to run with the injected environment variables (only used when `-o` is not specified)
|
|
105
106
|
|
|
106
|
-
For `aws secret` management subcommands (`create`, `update`, `
|
|
107
|
+
For `aws secret` management subcommands (`create`, `update`, `upsert`/`import`, `append`, `remove`, `list`, `get`, `value`, `delete`), use:
|
|
107
108
|
|
|
108
109
|
- `-r, --region <region>` to target a specific region
|
|
109
110
|
- `-p, --profile <profile>` to select credentials profile
|
|
@@ -111,8 +112,8 @@ For `aws secret` management subcommands (`create`, `update`, `append`, `remove`,
|
|
|
111
112
|
|
|
112
113
|
These options are honored consistently on `aws secret` subcommands.
|
|
113
114
|
|
|
114
|
-
`env-secrets aws -s` is for fetching/injecting secret values into a child process.
|
|
115
|
-
`env-secrets aws secret ...` is for lifecycle management commands (`create`, `update`, `
|
|
115
|
+
`env-secrets aws -s` is for fetching/injecting secret values into a child process. Use `--no-shell` to run the program directly without a shell wrapper (disables shell expansion).
|
|
116
|
+
`env-secrets aws secret ...` is for lifecycle management commands (`create`, `update`, `upsert`/`import`, `append`, `remove`, `list`, `get`, `value`, `delete`).
|
|
116
117
|
|
|
117
118
|
#### Examples
|
|
118
119
|
|
|
@@ -262,6 +263,19 @@ env-secrets aws secret append -n app/dev --key JIRA_EMAIL_TOKEN -v blah --output
|
|
|
262
263
|
env-secrets aws secret remove -n app/dev --key API_KEY --key OLD_TOKEN --output json
|
|
263
264
|
```
|
|
264
265
|
|
|
266
|
+
13. **View the values of a secret:**
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# Table output — values masked as **** by default
|
|
270
|
+
env-secrets aws secret value -n app/dev -r us-east-1
|
|
271
|
+
|
|
272
|
+
# Reveal actual values (warning printed to stderr)
|
|
273
|
+
env-secrets aws secret value -n app/dev -r us-east-1 --reveal
|
|
274
|
+
|
|
275
|
+
# JSON output — full values, suitable for scripting (warns if stdout is a terminal)
|
|
276
|
+
env-secrets aws secret value -n app/dev -r us-east-1 --output json
|
|
277
|
+
```
|
|
278
|
+
|
|
265
279
|
## Security Considerations
|
|
266
280
|
|
|
267
281
|
- 🔐 **Credential Management**: The tool respects AWS credential precedence (environment variables, IAM roles, profiles)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
import { cliWithEnv, cleanupTempFile } from './utils/test-utils';
|
|
6
|
+
import { registerAwsE2eContext } from './utils/aws-e2e-context';
|
|
7
|
+
|
|
8
|
+
describe('AWS Secret Value CLI Args', () => {
|
|
9
|
+
const { getLocalStackEnv } = registerAwsE2eContext();
|
|
10
|
+
|
|
11
|
+
test('should show masked values by default (table output)', async () => {
|
|
12
|
+
const secretName = `e2e-value-masked-${Date.now()}`;
|
|
13
|
+
const tempFile = path.join(
|
|
14
|
+
os.tmpdir(),
|
|
15
|
+
`env-secrets-value-${Date.now()}.env`
|
|
16
|
+
);
|
|
17
|
+
fs.writeFileSync(tempFile, 'DB_PASSWORD=super-secret\nAPI_KEY=abc123');
|
|
18
|
+
|
|
19
|
+
const createResult = await cliWithEnv(
|
|
20
|
+
['aws', 'secret', 'upsert', '--file', tempFile, '--name', secretName],
|
|
21
|
+
getLocalStackEnv()
|
|
22
|
+
);
|
|
23
|
+
expect(createResult.code).toBe(0);
|
|
24
|
+
|
|
25
|
+
const valueResult = await cliWithEnv(
|
|
26
|
+
['aws', 'secret', 'value', '-n', secretName],
|
|
27
|
+
getLocalStackEnv()
|
|
28
|
+
);
|
|
29
|
+
expect(valueResult.code).toBe(0);
|
|
30
|
+
expect(valueResult.stdout).toContain('DB_PASSWORD');
|
|
31
|
+
expect(valueResult.stdout).toContain('API_KEY');
|
|
32
|
+
expect(valueResult.stdout).toContain('****');
|
|
33
|
+
expect(valueResult.stdout).not.toContain('super-secret');
|
|
34
|
+
expect(valueResult.stdout).not.toContain('abc123');
|
|
35
|
+
|
|
36
|
+
const deleteResult1 = await cliWithEnv(
|
|
37
|
+
[
|
|
38
|
+
'aws',
|
|
39
|
+
'secret',
|
|
40
|
+
'delete',
|
|
41
|
+
'-n',
|
|
42
|
+
secretName,
|
|
43
|
+
'--force-delete-without-recovery',
|
|
44
|
+
'--yes'
|
|
45
|
+
],
|
|
46
|
+
getLocalStackEnv()
|
|
47
|
+
);
|
|
48
|
+
expect(deleteResult1.code).toBe(0);
|
|
49
|
+
cleanupTempFile(tempFile);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('should reveal values with --reveal flag and warn on stderr', async () => {
|
|
53
|
+
const secretName = `e2e-value-reveal-${Date.now()}`;
|
|
54
|
+
const tempFile = path.join(
|
|
55
|
+
os.tmpdir(),
|
|
56
|
+
`env-secrets-reveal-${Date.now()}.env`
|
|
57
|
+
);
|
|
58
|
+
fs.writeFileSync(tempFile, 'DB_PASSWORD=super-secret\nAPI_KEY=abc123');
|
|
59
|
+
|
|
60
|
+
const createResult = await cliWithEnv(
|
|
61
|
+
['aws', 'secret', 'upsert', '--file', tempFile, '--name', secretName],
|
|
62
|
+
getLocalStackEnv()
|
|
63
|
+
);
|
|
64
|
+
expect(createResult.code).toBe(0);
|
|
65
|
+
|
|
66
|
+
const valueResult = await cliWithEnv(
|
|
67
|
+
['aws', 'secret', 'value', '-n', secretName, '--reveal'],
|
|
68
|
+
getLocalStackEnv()
|
|
69
|
+
);
|
|
70
|
+
expect(valueResult.code).toBe(0);
|
|
71
|
+
expect(valueResult.stdout).toContain('DB_PASSWORD');
|
|
72
|
+
expect(valueResult.stdout).toContain('super-secret');
|
|
73
|
+
expect(valueResult.stdout).toContain('API_KEY');
|
|
74
|
+
expect(valueResult.stdout).toContain('abc123');
|
|
75
|
+
expect(valueResult.stderr).toContain(
|
|
76
|
+
'Warning: displaying sensitive secret values.'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const deleteResult2 = await cliWithEnv(
|
|
80
|
+
[
|
|
81
|
+
'aws',
|
|
82
|
+
'secret',
|
|
83
|
+
'delete',
|
|
84
|
+
'-n',
|
|
85
|
+
secretName,
|
|
86
|
+
'--force-delete-without-recovery',
|
|
87
|
+
'--yes'
|
|
88
|
+
],
|
|
89
|
+
getLocalStackEnv()
|
|
90
|
+
);
|
|
91
|
+
expect(deleteResult2.code).toBe(0);
|
|
92
|
+
cleanupTempFile(tempFile);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('should output full values as JSON without --reveal', async () => {
|
|
96
|
+
const secretName = `e2e-value-json-${Date.now()}`;
|
|
97
|
+
const tempFile = path.join(
|
|
98
|
+
os.tmpdir(),
|
|
99
|
+
`env-secrets-json-${Date.now()}.env`
|
|
100
|
+
);
|
|
101
|
+
fs.writeFileSync(tempFile, 'DB_PASSWORD=super-secret\nAPI_KEY=abc123');
|
|
102
|
+
|
|
103
|
+
const createResult = await cliWithEnv(
|
|
104
|
+
['aws', 'secret', 'upsert', '--file', tempFile, '--name', secretName],
|
|
105
|
+
getLocalStackEnv()
|
|
106
|
+
);
|
|
107
|
+
expect(createResult.code).toBe(0);
|
|
108
|
+
|
|
109
|
+
const valueResult = await cliWithEnv(
|
|
110
|
+
['aws', 'secret', 'value', '-n', secretName, '--output', 'json'],
|
|
111
|
+
getLocalStackEnv()
|
|
112
|
+
);
|
|
113
|
+
expect(valueResult.code).toBe(0);
|
|
114
|
+
const parsed = JSON.parse(valueResult.stdout) as Record<string, string>;
|
|
115
|
+
expect(parsed.DB_PASSWORD).toBe('super-secret');
|
|
116
|
+
expect(parsed.API_KEY).toBe('abc123');
|
|
117
|
+
|
|
118
|
+
const deleteResult3 = await cliWithEnv(
|
|
119
|
+
[
|
|
120
|
+
'aws',
|
|
121
|
+
'secret',
|
|
122
|
+
'delete',
|
|
123
|
+
'-n',
|
|
124
|
+
secretName,
|
|
125
|
+
'--force-delete-without-recovery',
|
|
126
|
+
'--yes'
|
|
127
|
+
],
|
|
128
|
+
getLocalStackEnv()
|
|
129
|
+
);
|
|
130
|
+
expect(deleteResult3.code).toBe(0);
|
|
131
|
+
cleanupTempFile(tempFile);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('should fail for a non-existent secret', async () => {
|
|
135
|
+
const valueResult = await cliWithEnv(
|
|
136
|
+
['aws', 'secret', 'value', '-n', 'does-not-exist-e2e'],
|
|
137
|
+
getLocalStackEnv()
|
|
138
|
+
);
|
|
139
|
+
expect(valueResult.code).not.toBe(0);
|
|
140
|
+
expect(valueResult.stderr).toContain('not found');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
readStdin,
|
|
12
12
|
renderTable,
|
|
13
13
|
resolveAwsScope,
|
|
14
|
+
resolveOutputFormat,
|
|
14
15
|
resolveSecretValue
|
|
15
16
|
} from '../../src/cli/helpers';
|
|
16
17
|
|
|
@@ -214,4 +215,38 @@ describe('cli/helpers', () => {
|
|
|
214
215
|
region: 'us-west-2'
|
|
215
216
|
});
|
|
216
217
|
});
|
|
218
|
+
|
|
219
|
+
describe('resolveOutputFormat', () => {
|
|
220
|
+
it('uses explicit local output option', () => {
|
|
221
|
+
expect(resolveOutputFormat({ output: 'json' })).toBe('json');
|
|
222
|
+
expect(resolveOutputFormat({ output: 'table' })).toBe('table');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('throws for invalid local output option', () => {
|
|
226
|
+
expect(() => resolveOutputFormat({ output: 'xml' })).toThrow(
|
|
227
|
+
'Invalid output format'
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('inherits json or table from global options', () => {
|
|
232
|
+
const jsonCmd = { optsWithGlobals: () => ({ output: 'json' }) };
|
|
233
|
+
const tableCmd = { optsWithGlobals: () => ({ output: 'table' }) };
|
|
234
|
+
expect(resolveOutputFormat({}, jsonCmd)).toBe('json');
|
|
235
|
+
expect(resolveOutputFormat({}, tableCmd)).toBe('table');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('ignores a file path in global output and defaults to table', () => {
|
|
239
|
+
const command = { optsWithGlobals: () => ({ output: 'secrets.env' }) };
|
|
240
|
+
expect(resolveOutputFormat({}, command)).toBe('table');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('defaults to table when no options are provided', () => {
|
|
244
|
+
expect(resolveOutputFormat({})).toBe('table');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('prefers local option over global option', () => {
|
|
248
|
+
const command = { optsWithGlobals: () => ({ output: 'json' }) };
|
|
249
|
+
expect(resolveOutputFormat({ output: 'table' }, command)).toBe('table');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
217
252
|
});
|
package/dist/cli/helpers.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.resolveAwsScope = exports.parseEnvSecretsFile = exports.parseEnvSecrets = exports.resolveSecretValue = exports.readStdin = exports.parseRecoveryDays = exports.printData = exports.renderTable = exports.asOutputFormat = void 0;
|
|
12
|
+
exports.resolveAwsScope = exports.resolveOutputFormat = exports.parseEnvSecretsFile = exports.parseEnvSecrets = exports.resolveSecretValue = exports.readStdin = exports.parseRecoveryDays = exports.printData = exports.renderTable = exports.asOutputFormat = void 0;
|
|
13
13
|
const promises_1 = require("node:fs/promises");
|
|
14
14
|
const asOutputFormat = (value) => {
|
|
15
15
|
if (value !== 'json' && value !== 'table') {
|
|
@@ -156,6 +156,18 @@ const parseEnvSecretsFile = (path) => __awaiter(void 0, void 0, void 0, function
|
|
|
156
156
|
return (0, exports.parseEnvSecrets)(content);
|
|
157
157
|
});
|
|
158
158
|
exports.parseEnvSecretsFile = parseEnvSecretsFile;
|
|
159
|
+
const resolveOutputFormat = (options, command) => {
|
|
160
|
+
var _a, _b;
|
|
161
|
+
if (options.output) {
|
|
162
|
+
return (0, exports.asOutputFormat)(options.output);
|
|
163
|
+
}
|
|
164
|
+
const globalOutput = (_b = (_a = command === null || command === void 0 ? void 0 : command.optsWithGlobals) === null || _a === void 0 ? void 0 : _a.call(command)) === null || _b === void 0 ? void 0 : _b.output;
|
|
165
|
+
if (globalOutput === 'json' || globalOutput === 'table') {
|
|
166
|
+
return globalOutput;
|
|
167
|
+
}
|
|
168
|
+
return 'table';
|
|
169
|
+
};
|
|
170
|
+
exports.resolveOutputFormat = resolveOutputFormat;
|
|
159
171
|
const resolveAwsScope = (options, command) => {
|
|
160
172
|
var _a;
|
|
161
173
|
const globalOptions = ((_a = command === null || command === void 0 ? void 0 : command.optsWithGlobals) === null || _a === void 0 ? void 0 : _a.call(command)) || {};
|
package/dist/index.js
CHANGED
|
@@ -159,13 +159,9 @@ secretCommand
|
|
|
159
159
|
.option('-r, --region <region>', 'region to use')
|
|
160
160
|
.option('--output <format>', 'output format: json|table')
|
|
161
161
|
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
162
|
-
var _a;
|
|
163
162
|
try {
|
|
164
163
|
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
165
|
-
const
|
|
166
|
-
const output = (_a = options.output) !== null && _a !== void 0 ? _a : (typeof globalOptions.output === 'string'
|
|
167
|
-
? globalOptions.output
|
|
168
|
-
: 'table');
|
|
164
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
169
165
|
const value = yield (0, helpers_1.resolveSecretValue)(options.value, options.valueStdin, options.file);
|
|
170
166
|
if (!value) {
|
|
171
167
|
throw new Error('Secret value is required. Provide --value, --value-stdin, or --file.');
|
|
@@ -203,13 +199,9 @@ secretCommand
|
|
|
203
199
|
.option('-r, --region <region>', 'region to use')
|
|
204
200
|
.option('--output <format>', 'output format: json|table')
|
|
205
201
|
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
206
|
-
var _b;
|
|
207
202
|
try {
|
|
208
203
|
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
209
|
-
const
|
|
210
|
-
const output = (_b = options.output) !== null && _b !== void 0 ? _b : (typeof globalOptions.output === 'string'
|
|
211
|
-
? globalOptions.output
|
|
212
|
-
: 'table');
|
|
204
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
213
205
|
const value = yield (0, helpers_1.resolveSecretValue)(options.value, options.valueStdin, options.file);
|
|
214
206
|
if (!value && !options.description && !options.kmsKeyId) {
|
|
215
207
|
throw new Error('Nothing to update. Provide --value/--value-stdin/--file, --description, or --kms-key-id.');
|
|
@@ -245,13 +237,9 @@ secretCommand
|
|
|
245
237
|
.option('-r, --region <region>', 'region to use')
|
|
246
238
|
.option('--output <format>', 'output format: json|table')
|
|
247
239
|
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
248
|
-
var _c;
|
|
249
240
|
try {
|
|
250
241
|
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
251
|
-
const
|
|
252
|
-
const output = (_c = options.output) !== null && _c !== void 0 ? _c : (typeof globalOptions.output === 'string'
|
|
253
|
-
? globalOptions.output
|
|
254
|
-
: 'table');
|
|
242
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
255
243
|
const parsed = yield (0, helpers_1.parseEnvSecretsFile)(options.file);
|
|
256
244
|
if (parsed.entries.length === 0) {
|
|
257
245
|
throw new Error('No env entries found. Include lines like KEY=value or export KEY=value.');
|
|
@@ -357,13 +345,9 @@ secretCommand
|
|
|
357
345
|
.option('-r, --region <region>', 'region to use')
|
|
358
346
|
.option('--output <format>', 'output format: json|table')
|
|
359
347
|
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
360
|
-
var _d;
|
|
361
348
|
try {
|
|
362
349
|
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
363
|
-
const
|
|
364
|
-
const output = (_d = options.output) !== null && _d !== void 0 ? _d : (typeof globalOptions.output === 'string'
|
|
365
|
-
? globalOptions.output
|
|
366
|
-
: 'table');
|
|
350
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
367
351
|
const value = yield (0, helpers_1.resolveSecretValue)(options.value, options.valueStdin, options.file);
|
|
368
352
|
if (!value) {
|
|
369
353
|
throw new Error('Append value is required. Provide --value, --value-stdin, or --file.');
|
|
@@ -404,13 +388,9 @@ secretCommand
|
|
|
404
388
|
.option('-r, --region <region>', 'region to use')
|
|
405
389
|
.option('--output <format>', 'output format: json|table')
|
|
406
390
|
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
407
|
-
var _e;
|
|
408
391
|
try {
|
|
409
392
|
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
410
|
-
const
|
|
411
|
-
const output = (_e = options.output) !== null && _e !== void 0 ? _e : (typeof globalOptions.output === 'string'
|
|
412
|
-
? globalOptions.output
|
|
413
|
-
: 'table');
|
|
393
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
414
394
|
const keys = options.key;
|
|
415
395
|
const current = yield (0, secretsmanager_admin_1.getSecretString)({
|
|
416
396
|
name: options.name,
|
|
@@ -461,13 +441,9 @@ secretCommand
|
|
|
461
441
|
.option('-r, --region <region>', 'region to use')
|
|
462
442
|
.option('--output <format>', 'output format: json|table')
|
|
463
443
|
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
464
|
-
var _f;
|
|
465
444
|
try {
|
|
466
445
|
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
467
|
-
const
|
|
468
|
-
const output = (_f = options.output) !== null && _f !== void 0 ? _f : (typeof globalOptions.output === 'string'
|
|
469
|
-
? globalOptions.output
|
|
470
|
-
: 'table');
|
|
446
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
471
447
|
const result = yield (0, secretsmanager_admin_1.listSecrets)({
|
|
472
448
|
prefix: options.prefix,
|
|
473
449
|
tags: options.tag,
|
|
@@ -497,13 +473,9 @@ secretCommand
|
|
|
497
473
|
.option('-r, --region <region>', 'region to use')
|
|
498
474
|
.option('--output <format>', 'output format: json|table')
|
|
499
475
|
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
500
|
-
var _g;
|
|
501
476
|
try {
|
|
502
477
|
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
503
|
-
const
|
|
504
|
-
const output = (_g = options.output) !== null && _g !== void 0 ? _g : (typeof globalOptions.output === 'string'
|
|
505
|
-
? globalOptions.output
|
|
506
|
-
: 'table');
|
|
478
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
507
479
|
const result = yield (0, secretsmanager_admin_1.getSecretMetadata)({
|
|
508
480
|
name: options.name,
|
|
509
481
|
profile,
|
|
@@ -532,6 +504,77 @@ secretCommand
|
|
|
532
504
|
exitWithError(error);
|
|
533
505
|
}
|
|
534
506
|
}));
|
|
507
|
+
secretCommand
|
|
508
|
+
.command('value')
|
|
509
|
+
.description('get the values of a secret')
|
|
510
|
+
.requiredOption('-n, --name <name>', 'secret name')
|
|
511
|
+
.option('--reveal', 'reveal secret values in table output (values are masked by default)', false)
|
|
512
|
+
.option('-p, --profile <profile>', 'profile to use')
|
|
513
|
+
.option('-r, --region <region>', 'region to use')
|
|
514
|
+
.option('--output <format>', 'output format: json|table')
|
|
515
|
+
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
516
|
+
try {
|
|
517
|
+
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
518
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
519
|
+
let secretString;
|
|
520
|
+
try {
|
|
521
|
+
secretString = yield (0, secretsmanager_admin_1.getSecretString)({
|
|
522
|
+
name: options.name,
|
|
523
|
+
profile,
|
|
524
|
+
region
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
529
|
+
if (msg.includes('cannot be edited with append/remove')) {
|
|
530
|
+
throw new Error(`Secret "${options.name}" is stored as binary and cannot be displayed as text.`);
|
|
531
|
+
}
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
let jsonEntries;
|
|
535
|
+
try {
|
|
536
|
+
const parsed = JSON.parse(secretString);
|
|
537
|
+
if (parsed && !Array.isArray(parsed) && typeof parsed === 'object') {
|
|
538
|
+
jsonEntries = Object.entries(parsed).map(([key, value]) => ({ key, value }));
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
jsonEntries = [{ key: options.name, value: secretString }];
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch (_a) {
|
|
545
|
+
jsonEntries = [{ key: options.name, value: secretString }];
|
|
546
|
+
}
|
|
547
|
+
if (output === 'json') {
|
|
548
|
+
if (process.stdout.isTTY) {
|
|
549
|
+
// eslint-disable-next-line no-console
|
|
550
|
+
console.error('Warning: displaying sensitive secret values.');
|
|
551
|
+
}
|
|
552
|
+
const result = Object.fromEntries(jsonEntries.map(({ key, value }) => [key, value]));
|
|
553
|
+
// eslint-disable-next-line no-console
|
|
554
|
+
console.log(JSON.stringify(result, null, 2));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (options.reveal) {
|
|
558
|
+
// eslint-disable-next-line no-console
|
|
559
|
+
console.error('Warning: displaying sensitive secret values.');
|
|
560
|
+
}
|
|
561
|
+
const rows = jsonEntries.map(({ key, value }) => ({
|
|
562
|
+
key,
|
|
563
|
+
value: options.reveal
|
|
564
|
+
? typeof value === 'string'
|
|
565
|
+
? value
|
|
566
|
+
: JSON.stringify(value)
|
|
567
|
+
: '****'
|
|
568
|
+
}));
|
|
569
|
+
(0, helpers_1.printData)((0, helpers_1.asOutputFormat)(output), [
|
|
570
|
+
{ key: 'key', label: 'Key' },
|
|
571
|
+
{ key: 'value', label: 'Value' }
|
|
572
|
+
], rows);
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
exitWithError(error);
|
|
576
|
+
}
|
|
577
|
+
}));
|
|
535
578
|
secretCommand
|
|
536
579
|
.command('delete')
|
|
537
580
|
.description('delete a secret in AWS Secrets Manager')
|
|
@@ -543,13 +586,9 @@ secretCommand
|
|
|
543
586
|
.option('-r, --region <region>', 'region to use')
|
|
544
587
|
.option('--output <format>', 'output format: json|table')
|
|
545
588
|
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
546
|
-
var _h;
|
|
547
589
|
try {
|
|
548
590
|
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
549
|
-
const
|
|
550
|
-
const output = (_h = options.output) !== null && _h !== void 0 ? _h : (typeof globalOptions.output === 'string'
|
|
551
|
-
? globalOptions.output
|
|
552
|
-
: 'table');
|
|
591
|
+
const output = (0, helpers_1.resolveOutputFormat)(options, command);
|
|
553
592
|
if (!options.yes) {
|
|
554
593
|
throw new Error('Delete requires --yes confirmation.');
|
|
555
594
|
}
|