env-secrets 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex/rules/cicd.md +170 -0
- package/.codex/rules/linting.md +174 -0
- package/.codex/rules/local-dev-badges.md +93 -0
- package/.codex/rules/local-dev-env.md +271 -0
- package/.codex/rules/local-dev-license.md +104 -0
- package/.codex/rules/local-dev-mcp.md +72 -0
- package/.codex/rules/logging.md +358 -0
- package/.codex/rules/observability.md +25 -0
- package/.codex/rules/testing.md +133 -0
- package/.github/workflows/lint.yaml +7 -8
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/unittests.yaml +1 -1
- package/AGENTS.md +10 -4
- package/README.md +14 -9
- package/__e2e__/README.md +2 -5
- package/__e2e__/index.test.ts +152 -1
- package/__e2e__/utils/test-utils.ts +61 -1
- package/__tests__/cli/helpers.test.ts +129 -0
- package/__tests__/vaults/aws-config.test.ts +85 -0
- package/__tests__/vaults/secretsmanager-admin.test.ts +312 -0
- package/__tests__/vaults/secretsmanager.test.ts +57 -20
- package/dist/cli/helpers.js +110 -0
- package/dist/index.js +221 -2
- package/dist/vaults/aws-config.js +29 -0
- package/dist/vaults/secretsmanager-admin.js +240 -0
- package/dist/vaults/secretsmanager.js +20 -16
- package/docs/AWS.md +78 -3
- package/eslint.config.js +67 -0
- package/jest.e2e.config.js +1 -0
- package/package.json +23 -13
- package/src/cli/helpers.ts +144 -0
- package/src/index.ts +287 -2
- package/src/vaults/aws-config.ts +51 -0
- package/src/vaults/secretsmanager-admin.ts +352 -0
- package/src/vaults/secretsmanager.ts +32 -20
- package/website/docs/cli-reference.mdx +67 -0
- package/website/docs/examples.mdx +1 -1
- package/website/docs/installation.mdx +1 -1
- package/website/docs/providers/aws-secrets-manager.mdx +32 -0
- package/.eslintignore +0 -4
- package/.eslintrc +0 -18
- package/.lintstagedrc +0 -4
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
|
2
|
+
import { STSClient } from '@aws-sdk/client-sts';
|
|
3
|
+
import { fromIni } from '@aws-sdk/credential-providers';
|
|
4
|
+
|
|
5
|
+
const mockSecretsManagerSend = jest.fn();
|
|
6
|
+
const mockSTSSend = jest.fn();
|
|
7
|
+
|
|
8
|
+
jest.mock('@aws-sdk/client-secrets-manager', () => {
|
|
9
|
+
return {
|
|
10
|
+
SecretsManagerClient: jest.fn().mockImplementation(() => ({
|
|
11
|
+
send: mockSecretsManagerSend
|
|
12
|
+
})),
|
|
13
|
+
CreateSecretCommand: jest.fn().mockImplementation((input) => ({ input })),
|
|
14
|
+
UpdateSecretCommand: jest.fn().mockImplementation((input) => ({ input })),
|
|
15
|
+
ListSecretsCommand: jest.fn().mockImplementation((input) => ({ input })),
|
|
16
|
+
DescribeSecretCommand: jest.fn().mockImplementation((input) => ({ input })),
|
|
17
|
+
DeleteSecretCommand: jest.fn().mockImplementation((input) => ({ input }))
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
jest.mock('@aws-sdk/client-sts', () => {
|
|
22
|
+
return {
|
|
23
|
+
STSClient: jest.fn().mockImplementation(() => ({
|
|
24
|
+
send: mockSTSSend
|
|
25
|
+
})),
|
|
26
|
+
GetCallerIdentityCommand: jest
|
|
27
|
+
.fn()
|
|
28
|
+
.mockImplementation((input) => ({ input }))
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
jest.mock('@aws-sdk/credential-providers');
|
|
33
|
+
|
|
34
|
+
import {
|
|
35
|
+
createSecret,
|
|
36
|
+
updateSecret,
|
|
37
|
+
listSecrets,
|
|
38
|
+
getSecretMetadata,
|
|
39
|
+
deleteSecret,
|
|
40
|
+
validateSecretName
|
|
41
|
+
} from '../../src/vaults/secretsmanager-admin';
|
|
42
|
+
|
|
43
|
+
const mockSecretsManagerClient = SecretsManagerClient as jest.MockedClass<
|
|
44
|
+
typeof SecretsManagerClient
|
|
45
|
+
>;
|
|
46
|
+
const mockSTSClient = STSClient as jest.MockedClass<typeof STSClient>;
|
|
47
|
+
const mockFromIni = fromIni as jest.MockedFunction<typeof fromIni>;
|
|
48
|
+
|
|
49
|
+
describe('secretsmanager-admin', () => {
|
|
50
|
+
let originalEnv: NodeJS.ProcessEnv;
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
jest.clearAllMocks();
|
|
54
|
+
originalEnv = { ...process.env };
|
|
55
|
+
process.env = { ...originalEnv };
|
|
56
|
+
mockSTSSend.mockResolvedValue({ Account: '123456789012' });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
process.env = originalEnv;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('validates secret names', () => {
|
|
64
|
+
expect(() => validateSecretName('valid/name_1')).not.toThrow();
|
|
65
|
+
expect(() => validateSecretName('invalid name')).toThrow(
|
|
66
|
+
'Invalid secret name'
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('creates a secret with tags', async () => {
|
|
71
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
72
|
+
Name: 'test-secret',
|
|
73
|
+
ARN: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret',
|
|
74
|
+
VersionId: 'v1'
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const result = await createSecret({
|
|
78
|
+
name: 'test-secret',
|
|
79
|
+
value: '{"API_KEY":"abc"}',
|
|
80
|
+
description: 'test',
|
|
81
|
+
tags: ['team=platform', 'env=dev'],
|
|
82
|
+
region: 'us-east-1'
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(mockSTSClient).toHaveBeenCalled();
|
|
86
|
+
expect(mockSecretsManagerClient).toHaveBeenCalled();
|
|
87
|
+
expect(mockSecretsManagerSend).toHaveBeenCalledWith(
|
|
88
|
+
expect.objectContaining({
|
|
89
|
+
input: expect.objectContaining({
|
|
90
|
+
Name: 'test-secret',
|
|
91
|
+
SecretString: '{"API_KEY":"abc"}',
|
|
92
|
+
Tags: [
|
|
93
|
+
{ Key: 'team', Value: 'platform' },
|
|
94
|
+
{ Key: 'env', Value: 'dev' }
|
|
95
|
+
]
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
expect(result).toEqual({
|
|
100
|
+
arn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret',
|
|
101
|
+
name: 'test-secret',
|
|
102
|
+
versionId: 'v1'
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('supports tags with multiple equals signs', async () => {
|
|
107
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
108
|
+
Name: 'test-secret',
|
|
109
|
+
ARN: 'arn:test',
|
|
110
|
+
VersionId: 'v1'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await createSecret({
|
|
114
|
+
name: 'test-secret',
|
|
115
|
+
value: 'value',
|
|
116
|
+
tags: ['url=https://example.com?a=1'],
|
|
117
|
+
region: 'us-east-1'
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(mockSecretsManagerSend).toHaveBeenCalledWith(
|
|
121
|
+
expect.objectContaining({
|
|
122
|
+
input: expect.objectContaining({
|
|
123
|
+
Tags: [{ Key: 'url', Value: 'https://example.com?a=1' }]
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('throws for malformed tags', async () => {
|
|
130
|
+
await expect(
|
|
131
|
+
createSecret({
|
|
132
|
+
name: 'test-secret',
|
|
133
|
+
value: 'value',
|
|
134
|
+
tags: ['invalid-tag'],
|
|
135
|
+
region: 'us-east-1'
|
|
136
|
+
})
|
|
137
|
+
).rejects.toThrow('Invalid tag format');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('omits tags when none are provided', async () => {
|
|
141
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
142
|
+
Name: 'test-secret',
|
|
143
|
+
ARN: 'arn:test',
|
|
144
|
+
VersionId: 'v1'
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await createSecret({
|
|
148
|
+
name: 'test-secret',
|
|
149
|
+
value: 'value',
|
|
150
|
+
region: 'us-east-1'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(mockSecretsManagerSend).toHaveBeenCalledWith(
|
|
154
|
+
expect.objectContaining({
|
|
155
|
+
input: expect.objectContaining({
|
|
156
|
+
Tags: undefined
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('maps create secret AlreadyExistsException', async () => {
|
|
163
|
+
mockSecretsManagerSend.mockRejectedValueOnce({
|
|
164
|
+
name: 'AlreadyExistsException'
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await expect(
|
|
168
|
+
createSecret({
|
|
169
|
+
name: 'existing-secret',
|
|
170
|
+
value: 'abc',
|
|
171
|
+
region: 'us-east-1'
|
|
172
|
+
})
|
|
173
|
+
).rejects.toThrow('already exists');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('updates a secret value and description', async () => {
|
|
177
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
178
|
+
Name: 'test-secret',
|
|
179
|
+
ARN: 'arn:test',
|
|
180
|
+
VersionId: 'v2'
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const result = await updateSecret({
|
|
184
|
+
name: 'test-secret',
|
|
185
|
+
value: '{"API_KEY":"new"}',
|
|
186
|
+
description: 'updated',
|
|
187
|
+
region: 'us-east-1'
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(mockSecretsManagerSend).toHaveBeenCalledWith(
|
|
191
|
+
expect.objectContaining({
|
|
192
|
+
input: expect.objectContaining({
|
|
193
|
+
SecretId: 'test-secret',
|
|
194
|
+
SecretString: '{"API_KEY":"new"}',
|
|
195
|
+
Description: 'updated'
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
expect(result.versionId).toBe('v2');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('lists secrets and applies prefix/tag filters', async () => {
|
|
203
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
204
|
+
SecretList: [
|
|
205
|
+
{
|
|
206
|
+
Name: 'app/one',
|
|
207
|
+
Description: 'One',
|
|
208
|
+
Tags: [{ Key: 'env', Value: 'dev' }],
|
|
209
|
+
LastChangedDate: new Date('2026-02-16T00:00:00.000Z')
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
Name: 'app/two',
|
|
213
|
+
Description: 'Two',
|
|
214
|
+
Tags: [{ Key: 'env', Value: 'prod' }],
|
|
215
|
+
LastChangedDate: new Date('2026-02-16T00:00:00.000Z')
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const result = await listSecrets({
|
|
221
|
+
prefix: 'app/',
|
|
222
|
+
tags: ['env=dev'],
|
|
223
|
+
region: 'us-east-1'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(result).toHaveLength(1);
|
|
227
|
+
expect(result[0].name).toBe('app/one');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('ignores invalid AWS tags during tag filtering', async () => {
|
|
231
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
232
|
+
SecretList: [
|
|
233
|
+
{
|
|
234
|
+
Name: 'app/one',
|
|
235
|
+
Tags: [{ Key: undefined, Value: 'dev' }]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
Name: 'app/two',
|
|
239
|
+
Tags: [{ Key: 'env', Value: 'dev' }]
|
|
240
|
+
}
|
|
241
|
+
]
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const result = await listSecrets({
|
|
245
|
+
tags: ['env=dev'],
|
|
246
|
+
region: 'us-east-1'
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(result).toHaveLength(1);
|
|
250
|
+
expect(result[0].name).toBe('app/two');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('returns secret metadata without secret value', async () => {
|
|
254
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
255
|
+
Name: 'app/meta',
|
|
256
|
+
ARN: 'arn:meta',
|
|
257
|
+
Description: 'Meta secret',
|
|
258
|
+
KmsKeyId: 'kms-key',
|
|
259
|
+
CreatedDate: new Date('2026-02-16T00:00:00.000Z'),
|
|
260
|
+
LastChangedDate: new Date('2026-02-16T01:00:00.000Z'),
|
|
261
|
+
VersionIdsToStages: { abc: ['AWSCURRENT'] },
|
|
262
|
+
Tags: [{ Key: 'env', Value: 'dev' }]
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const result = await getSecretMetadata({
|
|
266
|
+
name: 'app/meta',
|
|
267
|
+
region: 'us-east-1'
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(result).toEqual(
|
|
271
|
+
expect.objectContaining({
|
|
272
|
+
name: 'app/meta',
|
|
273
|
+
arn: 'arn:meta',
|
|
274
|
+
tags: { env: 'dev' },
|
|
275
|
+
versionIdsToStages: { abc: ['AWSCURRENT'] }
|
|
276
|
+
})
|
|
277
|
+
);
|
|
278
|
+
expect(Object.keys(result)).not.toContain('secretString');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('deletes secret with recovery options', async () => {
|
|
282
|
+
const mockCredentials = jest.fn().mockResolvedValue({
|
|
283
|
+
accessKeyId: 'default',
|
|
284
|
+
secretAccessKey: 'default'
|
|
285
|
+
});
|
|
286
|
+
mockFromIni.mockReturnValue(mockCredentials);
|
|
287
|
+
|
|
288
|
+
mockSecretsManagerSend.mockResolvedValueOnce({
|
|
289
|
+
Name: 'app/delete',
|
|
290
|
+
ARN: 'arn:delete',
|
|
291
|
+
DeletionDate: new Date('2026-02-16T02:00:00.000Z')
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const result = await deleteSecret({
|
|
295
|
+
name: 'app/delete',
|
|
296
|
+
recoveryDays: 7,
|
|
297
|
+
profile: 'test-profile',
|
|
298
|
+
region: 'us-east-1'
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(mockFromIni).toHaveBeenCalledWith({ profile: 'test-profile' });
|
|
302
|
+
expect(mockSecretsManagerSend).toHaveBeenCalledWith(
|
|
303
|
+
expect.objectContaining({
|
|
304
|
+
input: expect.objectContaining({
|
|
305
|
+
SecretId: 'app/delete',
|
|
306
|
+
RecoveryWindowInDays: 7
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
expect(result.name).toBe('app/delete');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
GetSecretValueCommand
|
|
4
|
-
} from '@aws-sdk/client-secrets-manager';
|
|
5
|
-
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
|
1
|
+
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
|
2
|
+
import { STSClient } from '@aws-sdk/client-sts';
|
|
6
3
|
import { fromIni } from '@aws-sdk/credential-providers';
|
|
7
4
|
|
|
5
|
+
// Mock send functions - must be declared before jest.mock calls
|
|
6
|
+
const mockSecretsManagerSend = jest.fn();
|
|
7
|
+
const mockSTSSend = jest.fn();
|
|
8
|
+
|
|
8
9
|
// Mock the AWS SDK and dependencies
|
|
9
|
-
jest.mock('@aws-sdk/client-secrets-manager')
|
|
10
|
-
|
|
10
|
+
jest.mock('@aws-sdk/client-secrets-manager', () => {
|
|
11
|
+
return {
|
|
12
|
+
SecretsManagerClient: jest.fn().mockImplementation(() => ({
|
|
13
|
+
send: mockSecretsManagerSend
|
|
14
|
+
})),
|
|
15
|
+
GetSecretValueCommand: jest.fn().mockImplementation((input) => ({
|
|
16
|
+
input
|
|
17
|
+
}))
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
jest.mock('@aws-sdk/client-sts', () => {
|
|
22
|
+
return {
|
|
23
|
+
STSClient: jest.fn().mockImplementation(() => ({
|
|
24
|
+
send: mockSTSSend
|
|
25
|
+
})),
|
|
26
|
+
GetCallerIdentityCommand: jest.fn().mockImplementation((input) => ({
|
|
27
|
+
input
|
|
28
|
+
}))
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
11
32
|
jest.mock('@aws-sdk/credential-providers');
|
|
12
33
|
jest.mock('debug', () => {
|
|
13
34
|
const mockDebug = jest.fn();
|
|
@@ -27,8 +48,6 @@ const mockFromIni = fromIni as jest.MockedFunction<typeof fromIni>;
|
|
|
27
48
|
|
|
28
49
|
describe('secretsmanager', () => {
|
|
29
50
|
let originalEnv: NodeJS.ProcessEnv;
|
|
30
|
-
let mockSecretsManagerSend: jest.MockedFunction<any>;
|
|
31
|
-
let mockSTSSend: jest.MockedFunction<any>;
|
|
32
51
|
|
|
33
52
|
beforeEach(() => {
|
|
34
53
|
// Clear all mocks
|
|
@@ -37,13 +56,6 @@ describe('secretsmanager', () => {
|
|
|
37
56
|
// Reset process.env
|
|
38
57
|
originalEnv = { ...process.env };
|
|
39
58
|
process.env = { ...originalEnv };
|
|
40
|
-
|
|
41
|
-
// Setup AWS client mocks
|
|
42
|
-
mockSecretsManagerSend = jest.fn();
|
|
43
|
-
mockSTSSend = jest.fn();
|
|
44
|
-
|
|
45
|
-
mockSecretsManagerClient.prototype.send = mockSecretsManagerSend;
|
|
46
|
-
mockSTSClient.prototype.send = mockSTSSend;
|
|
47
59
|
});
|
|
48
60
|
|
|
49
61
|
afterEach(() => {
|
|
@@ -72,10 +84,10 @@ describe('secretsmanager', () => {
|
|
|
72
84
|
});
|
|
73
85
|
|
|
74
86
|
expect(mockSTSSend).toHaveBeenCalledWith(
|
|
75
|
-
expect.
|
|
87
|
+
expect.objectContaining({ input: {} })
|
|
76
88
|
);
|
|
77
89
|
expect(mockSecretsManagerSend).toHaveBeenCalledWith(
|
|
78
|
-
expect.
|
|
90
|
+
expect.objectContaining({ input: expect.anything() })
|
|
79
91
|
);
|
|
80
92
|
expect(result).toEqual({
|
|
81
93
|
API_KEY: 'test-key',
|
|
@@ -93,11 +105,33 @@ describe('secretsmanager', () => {
|
|
|
93
105
|
});
|
|
94
106
|
|
|
95
107
|
expect(mockSTSSend).toHaveBeenCalledWith(
|
|
96
|
-
expect.
|
|
108
|
+
expect.objectContaining({ input: {} })
|
|
97
109
|
);
|
|
98
110
|
expect(mockSecretsManagerSend).not.toHaveBeenCalled();
|
|
99
111
|
expect(result).toEqual({});
|
|
100
112
|
});
|
|
113
|
+
|
|
114
|
+
it('should surface a clean error when profile credentials cannot be loaded', async () => {
|
|
115
|
+
const consoleErrorSpy = jest
|
|
116
|
+
.spyOn(console, 'error')
|
|
117
|
+
.mockImplementation(() => undefined);
|
|
118
|
+
mockSTSSend.mockRejectedValueOnce({
|
|
119
|
+
name: 'CredentialsProviderError',
|
|
120
|
+
message: 'Could not load credentials from any providers'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const result = await secretsmanager({
|
|
124
|
+
secret: 'my-secret',
|
|
125
|
+
profile: 'missing-profile',
|
|
126
|
+
region: 'us-east-1'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
130
|
+
'Could not load credentials from any providers'
|
|
131
|
+
);
|
|
132
|
+
expect(result).toEqual({});
|
|
133
|
+
expect(mockSecretsManagerSend).not.toHaveBeenCalled();
|
|
134
|
+
});
|
|
101
135
|
});
|
|
102
136
|
|
|
103
137
|
describe('credential handling', () => {
|
|
@@ -426,7 +460,10 @@ describe('secretsmanager', () => {
|
|
|
426
460
|
});
|
|
427
461
|
|
|
428
462
|
expect(mockFromIni).toHaveBeenCalledWith({ profile: 'production' });
|
|
429
|
-
expect(mockSTSClient).toHaveBeenCalledWith({
|
|
463
|
+
expect(mockSTSClient).toHaveBeenCalledWith({
|
|
464
|
+
region: 'us-west-2',
|
|
465
|
+
credentials: mockCredentials
|
|
466
|
+
});
|
|
430
467
|
expect(mockSecretsManagerClient).toHaveBeenCalledWith({
|
|
431
468
|
region: 'us-west-2',
|
|
432
469
|
credentials: mockCredentials
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.resolveAwsScope = exports.resolveSecretValue = exports.readStdin = exports.parseRecoveryDays = exports.printData = exports.renderTable = exports.asOutputFormat = void 0;
|
|
13
|
+
const asOutputFormat = (value) => {
|
|
14
|
+
if (value !== 'json' && value !== 'table') {
|
|
15
|
+
throw new Error(`Invalid output format "${value}". Use "json" or "table".`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
};
|
|
19
|
+
exports.asOutputFormat = asOutputFormat;
|
|
20
|
+
const renderTable = (headers, rows) => {
|
|
21
|
+
if (rows.length === 0) {
|
|
22
|
+
return 'No results.';
|
|
23
|
+
}
|
|
24
|
+
const widths = headers.map((header) => {
|
|
25
|
+
return Math.max(header.label.length, ...rows.map((row) => String(row[header.key] || '').length));
|
|
26
|
+
});
|
|
27
|
+
const headerLine = headers
|
|
28
|
+
.map((header, index) => header.label.padEnd(widths[index]))
|
|
29
|
+
.join(' ');
|
|
30
|
+
const divider = headers
|
|
31
|
+
.map((_, index) => '-'.repeat(widths[index]))
|
|
32
|
+
.join(' ');
|
|
33
|
+
const lines = rows.map((row) => headers
|
|
34
|
+
.map((header, index) => String(row[header.key] || '').padEnd(widths[index]))
|
|
35
|
+
.join(' '));
|
|
36
|
+
return [headerLine, divider, ...lines].join('\n');
|
|
37
|
+
};
|
|
38
|
+
exports.renderTable = renderTable;
|
|
39
|
+
const printData = (format, headers, rows) => {
|
|
40
|
+
if (format === 'json') {
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.log((0, exports.renderTable)(headers, rows));
|
|
47
|
+
};
|
|
48
|
+
exports.printData = printData;
|
|
49
|
+
const parseRecoveryDays = (value) => {
|
|
50
|
+
const parsed = Number(value);
|
|
51
|
+
if (!Number.isInteger(parsed) || parsed < 7 || parsed > 30) {
|
|
52
|
+
throw new Error('Recovery days must be an integer between 7 and 30.');
|
|
53
|
+
}
|
|
54
|
+
return parsed;
|
|
55
|
+
};
|
|
56
|
+
exports.parseRecoveryDays = parseRecoveryDays;
|
|
57
|
+
const readStdin = (stdin = process.stdin) => __awaiter(void 0, void 0, void 0, function* () {
|
|
58
|
+
const chunks = [];
|
|
59
|
+
return yield new Promise((resolve, reject) => {
|
|
60
|
+
const onData = (chunk) => {
|
|
61
|
+
chunks.push(chunk);
|
|
62
|
+
};
|
|
63
|
+
const onEnd = () => {
|
|
64
|
+
cleanup();
|
|
65
|
+
resolve(Buffer.concat(chunks)
|
|
66
|
+
.toString('utf8')
|
|
67
|
+
.replace(/\r?\n$/, ''));
|
|
68
|
+
};
|
|
69
|
+
const onError = (error) => {
|
|
70
|
+
cleanup();
|
|
71
|
+
reject(error);
|
|
72
|
+
};
|
|
73
|
+
const cleanup = () => {
|
|
74
|
+
stdin.off('data', onData);
|
|
75
|
+
stdin.off('end', onEnd);
|
|
76
|
+
stdin.off('error', onError);
|
|
77
|
+
};
|
|
78
|
+
stdin.on('data', onData);
|
|
79
|
+
stdin.once('end', onEnd);
|
|
80
|
+
stdin.once('error', onError);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
exports.readStdin = readStdin;
|
|
84
|
+
const resolveSecretValue = (value, valueStdin) => __awaiter(void 0, void 0, void 0, function* () {
|
|
85
|
+
if (value && valueStdin) {
|
|
86
|
+
throw new Error('Use either --value or --value-stdin, not both.');
|
|
87
|
+
}
|
|
88
|
+
if (valueStdin) {
|
|
89
|
+
if (process.stdin.isTTY) {
|
|
90
|
+
throw new Error('No stdin detected. Pipe a value when using --value-stdin.');
|
|
91
|
+
}
|
|
92
|
+
return yield (0, exports.readStdin)();
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
});
|
|
96
|
+
exports.resolveSecretValue = resolveSecretValue;
|
|
97
|
+
const resolveAwsScope = (options, command) => {
|
|
98
|
+
var _a;
|
|
99
|
+
const globalOptions = ((_a = command === null || command === void 0 ? void 0 : command.optsWithGlobals) === null || _a === void 0 ? void 0 : _a.call(command)) || {};
|
|
100
|
+
const profile = options.profile ||
|
|
101
|
+
(typeof globalOptions.profile === 'string'
|
|
102
|
+
? globalOptions.profile
|
|
103
|
+
: undefined);
|
|
104
|
+
const region = options.region ||
|
|
105
|
+
(typeof globalOptions.region === 'string'
|
|
106
|
+
? globalOptions.region
|
|
107
|
+
: undefined);
|
|
108
|
+
return { profile, region };
|
|
109
|
+
};
|
|
110
|
+
exports.resolveAwsScope = resolveAwsScope;
|