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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "env-secrets",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
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)",
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,