env-secrets 0.5.1 → 0.5.2
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 +6 -0
- package/__e2e__/aws-secret-lifecycle.test.ts +60 -1
- package/dist/index.js +36 -1
- package/docs/AWS.md +7 -0
- package/package.json +1 -1
- package/src/index.ts +45 -1
package/README.md
CHANGED
|
@@ -246,6 +246,12 @@ env-secrets aws secret upsert --file .env --name app/dev --output json
|
|
|
246
246
|
# {"NAME":"secret1"}
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
+
`env-secrets aws secret create` also always stores `SecretString` as a JSON object:
|
|
250
|
+
|
|
251
|
+
- object JSON input is preserved semantically as an object (formatting and key order may change)
|
|
252
|
+
- dotenv-style input is converted into key/value pairs
|
|
253
|
+
- scalar input is wrapped as `{"value":"..."}`.
|
|
254
|
+
|
|
249
255
|
12. **Append/remove keys on an existing JSON secret:**
|
|
250
256
|
|
|
251
257
|
```bash
|
|
@@ -5,7 +5,8 @@ import * as path from 'path';
|
|
|
5
5
|
import {
|
|
6
6
|
cliWithEnv,
|
|
7
7
|
cliWithEnvAndStdin,
|
|
8
|
-
cleanupTempFile
|
|
8
|
+
cleanupTempFile,
|
|
9
|
+
execAwslocalCommand
|
|
9
10
|
} from './utils/test-utils';
|
|
10
11
|
import { registerAwsE2eContext } from './utils/aws-e2e-context';
|
|
11
12
|
|
|
@@ -38,6 +39,14 @@ describe('AWS Secret Subcommand Lifecycle Args', () => {
|
|
|
38
39
|
expect(getResult.stdout).toContain(secretName);
|
|
39
40
|
expect(getResult.stdout).not.toContain('initial-value');
|
|
40
41
|
|
|
42
|
+
const createdSecret = await execAwslocalCommand(
|
|
43
|
+
`awslocal secretsmanager get-secret-value --secret-id "${secretName}" --region us-east-1 --query SecretString --output text`,
|
|
44
|
+
getLocalStackEnv()
|
|
45
|
+
);
|
|
46
|
+
expect(JSON.parse(createdSecret.stdout.trim())).toEqual({
|
|
47
|
+
value: 'initial-value'
|
|
48
|
+
});
|
|
49
|
+
|
|
41
50
|
const deleteResult = await cliWithEnv(
|
|
42
51
|
[
|
|
43
52
|
'aws',
|
|
@@ -84,6 +93,39 @@ describe('AWS Secret Subcommand Lifecycle Args', () => {
|
|
|
84
93
|
expect(deleteResult.code).toBe(0);
|
|
85
94
|
});
|
|
86
95
|
|
|
96
|
+
test('should create a json secret object from stdin', async () => {
|
|
97
|
+
const secretName = `managed-secret-create-stdin-${Date.now()}`;
|
|
98
|
+
|
|
99
|
+
const createResult = await cliWithEnvAndStdin(
|
|
100
|
+
['aws', 'secret', 'create', '-n', secretName, '--value-stdin'],
|
|
101
|
+
getLocalStackEnv(),
|
|
102
|
+
'stdin-create-value'
|
|
103
|
+
);
|
|
104
|
+
expect(createResult.code).toBe(0);
|
|
105
|
+
|
|
106
|
+
const createdSecret = await execAwslocalCommand(
|
|
107
|
+
`awslocal secretsmanager get-secret-value --secret-id "${secretName}" --region us-east-1 --query SecretString --output text`,
|
|
108
|
+
getLocalStackEnv()
|
|
109
|
+
);
|
|
110
|
+
expect(JSON.parse(createdSecret.stdout.trim())).toEqual({
|
|
111
|
+
value: 'stdin-create-value'
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const deleteResult = await cliWithEnv(
|
|
115
|
+
[
|
|
116
|
+
'aws',
|
|
117
|
+
'secret',
|
|
118
|
+
'delete',
|
|
119
|
+
'-n',
|
|
120
|
+
secretName,
|
|
121
|
+
'--force-delete-without-recovery',
|
|
122
|
+
'--yes'
|
|
123
|
+
],
|
|
124
|
+
getLocalStackEnv()
|
|
125
|
+
);
|
|
126
|
+
expect(deleteResult.code).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
87
129
|
test('should create from a single-line env file and retrieve via aws -s', async () => {
|
|
88
130
|
const secretName = `managed-secret-create-single-file-${Date.now()}`;
|
|
89
131
|
const tempFile = path.join(
|
|
@@ -119,6 +161,14 @@ describe('AWS Secret Subcommand Lifecycle Args', () => {
|
|
|
119
161
|
>;
|
|
120
162
|
expect(envVars.GITHUB_PAT).toBe('github_pat_single_line');
|
|
121
163
|
|
|
164
|
+
const createdSecret = await execAwslocalCommand(
|
|
165
|
+
`awslocal secretsmanager get-secret-value --secret-id "${secretName}" --region us-east-1 --query SecretString --output text`,
|
|
166
|
+
getLocalStackEnv()
|
|
167
|
+
);
|
|
168
|
+
expect(JSON.parse(createdSecret.stdout.trim())).toEqual({
|
|
169
|
+
GITHUB_PAT: 'github_pat_single_line'
|
|
170
|
+
});
|
|
171
|
+
|
|
122
172
|
const deleteResult = await cliWithEnv(
|
|
123
173
|
[
|
|
124
174
|
'aws',
|
|
@@ -176,6 +226,15 @@ describe('AWS Secret Subcommand Lifecycle Args', () => {
|
|
|
176
226
|
expect(envVars.GITHUB_PAT).toBe('github_pat_multi_line');
|
|
177
227
|
expect(envVars.API_URL).toBe('https://example.com');
|
|
178
228
|
|
|
229
|
+
const createdSecret = await execAwslocalCommand(
|
|
230
|
+
`awslocal secretsmanager get-secret-value --secret-id "${secretName}" --region us-east-1 --query SecretString --output text`,
|
|
231
|
+
getLocalStackEnv()
|
|
232
|
+
);
|
|
233
|
+
expect(JSON.parse(createdSecret.stdout.trim())).toEqual({
|
|
234
|
+
GITHUB_PAT: 'github_pat_multi_line',
|
|
235
|
+
API_URL: 'https://example.com'
|
|
236
|
+
});
|
|
237
|
+
|
|
179
238
|
const deleteResult = await cliWithEnv(
|
|
180
239
|
[
|
|
181
240
|
'aws',
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,40 @@ const parseSecretJsonObject = (secretName, value) => {
|
|
|
43
43
|
}
|
|
44
44
|
return parsed;
|
|
45
45
|
};
|
|
46
|
+
const parseEnvToObject = (value) => {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = (0, helpers_1.parseEnvSecrets)(value);
|
|
49
|
+
if (parsed.entries.length === 0) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return Object.fromEntries(parsed.entries.map((entry) => [entry.key, entry.value]));
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const toSecretJsonObject = (value) => {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(value);
|
|
61
|
+
if (parsed && !Array.isArray(parsed) && typeof parsed === 'object') {
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
if (typeof parsed === 'string') {
|
|
65
|
+
const envPayload = parseEnvToObject(parsed);
|
|
66
|
+
if (envPayload) {
|
|
67
|
+
return envPayload;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { value: parsed };
|
|
71
|
+
}
|
|
72
|
+
catch (_a) {
|
|
73
|
+
const envPayload = parseEnvToObject(value);
|
|
74
|
+
if (envPayload) {
|
|
75
|
+
return envPayload;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { value };
|
|
79
|
+
};
|
|
46
80
|
// main program
|
|
47
81
|
program
|
|
48
82
|
.name('env-secrets')
|
|
@@ -125,9 +159,10 @@ secretCommand
|
|
|
125
159
|
if (!value) {
|
|
126
160
|
throw new Error('Secret value is required. Provide --value, --value-stdin, or --file.');
|
|
127
161
|
}
|
|
162
|
+
const payload = toSecretJsonObject(value);
|
|
128
163
|
const result = yield (0, secretsmanager_admin_1.createSecret)({
|
|
129
164
|
name: options.name,
|
|
130
|
-
value,
|
|
165
|
+
value: JSON.stringify(payload),
|
|
131
166
|
description: options.description,
|
|
132
167
|
kmsKeyId: options.kmsKeyId,
|
|
133
168
|
tags: options.tag,
|
package/docs/AWS.md
CHANGED
|
@@ -200,6 +200,12 @@ source secrets.env
|
|
|
200
200
|
--output json
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
+
`create` always writes a JSON object:
|
|
204
|
+
|
|
205
|
+
- object JSON input is preserved semantically as an object (formatting and key order may change) (`{"API_KEY":"abc123"}`)
|
|
206
|
+
- dotenv-style input is converted (`KEY=value` -> `{"KEY":"value"}`)
|
|
207
|
+
- non-object/scalar input is wrapped (`super-secret-value` -> `{"value":"super-secret-value"}`)
|
|
208
|
+
|
|
203
209
|
2. **Create from stdin (recommended for sensitive values):**
|
|
204
210
|
|
|
205
211
|
```bash
|
|
@@ -262,6 +268,7 @@ source secrets.env
|
|
|
262
268
|
|
|
263
269
|
- `delete` requires `--yes`.
|
|
264
270
|
- `create`/`update` accept `--value`, `--value-stdin`, or `--file` (use only one).
|
|
271
|
+
- `create` always stores `SecretString` as a JSON object.
|
|
265
272
|
- `append` and `remove` require the secret value to be a JSON object.
|
|
266
273
|
- `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`.
|
|
267
274
|
- Use `--value-stdin` to avoid shell history leakage for sensitive values.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from './vaults/secretsmanager-admin';
|
|
19
19
|
import {
|
|
20
20
|
asOutputFormat,
|
|
21
|
+
parseEnvSecrets,
|
|
21
22
|
parseEnvSecretsFile,
|
|
22
23
|
printData,
|
|
23
24
|
parseRecoveryDays,
|
|
@@ -58,6 +59,48 @@ const parseSecretJsonObject = (
|
|
|
58
59
|
return parsed as Record<string, unknown>;
|
|
59
60
|
};
|
|
60
61
|
|
|
62
|
+
const parseEnvToObject = (
|
|
63
|
+
value: string
|
|
64
|
+
): Record<string, unknown> | undefined => {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = parseEnvSecrets(value);
|
|
67
|
+
if (parsed.entries.length === 0) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Object.fromEntries(
|
|
72
|
+
parsed.entries.map((entry) => [entry.key, entry.value])
|
|
73
|
+
);
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const toSecretJsonObject = (value: string): Record<string, unknown> => {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(value) as unknown;
|
|
82
|
+
if (parsed && !Array.isArray(parsed) && typeof parsed === 'object') {
|
|
83
|
+
return parsed as Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof parsed === 'string') {
|
|
87
|
+
const envPayload = parseEnvToObject(parsed);
|
|
88
|
+
if (envPayload) {
|
|
89
|
+
return envPayload;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { value: parsed };
|
|
94
|
+
} catch {
|
|
95
|
+
const envPayload = parseEnvToObject(value);
|
|
96
|
+
if (envPayload) {
|
|
97
|
+
return envPayload;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { value };
|
|
102
|
+
};
|
|
103
|
+
|
|
61
104
|
// main program
|
|
62
105
|
program
|
|
63
106
|
.name('env-secrets')
|
|
@@ -166,9 +209,10 @@ secretCommand
|
|
|
166
209
|
);
|
|
167
210
|
}
|
|
168
211
|
|
|
212
|
+
const payload = toSecretJsonObject(value);
|
|
169
213
|
const result = await createSecret({
|
|
170
214
|
name: options.name,
|
|
171
|
-
value,
|
|
215
|
+
value: JSON.stringify(payload),
|
|
172
216
|
description: options.description,
|
|
173
217
|
kmsKeyId: options.kmsKeyId,
|
|
174
218
|
tags: options.tag,
|