env-secrets 0.1.10 → 0.3.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.
Files changed (61) hide show
  1. package/.devcontainer/devcontainer.json +33 -0
  2. package/.dockerignore +9 -0
  3. package/.eslintignore +4 -2
  4. package/.github/dependabot.yml +4 -0
  5. package/.github/workflows/build-main.yml +6 -2
  6. package/.github/workflows/deploy-docs.yml +50 -0
  7. package/.github/workflows/e2e-tests.yaml +54 -0
  8. package/.github/workflows/lint.yaml +6 -2
  9. package/.github/workflows/release.yml +13 -3
  10. package/.github/workflows/snyk.yaml +5 -1
  11. package/.github/workflows/unittests.yaml +18 -6
  12. package/.lintstagedrc +2 -7
  13. package/.prettierignore +6 -0
  14. package/AGENTS.md +149 -0
  15. package/Dockerfile +14 -0
  16. package/README.md +507 -36
  17. package/__e2e__/README.md +160 -0
  18. package/__e2e__/index.test.ts +339 -0
  19. package/__e2e__/setup.ts +58 -0
  20. package/__e2e__/utils/debug-logger.ts +45 -0
  21. package/__e2e__/utils/test-utils.ts +645 -0
  22. package/__tests__/index.test.ts +573 -31
  23. package/__tests__/vaults/secretsmanager.test.ts +460 -0
  24. package/__tests__/vaults/utils.test.ts +183 -0
  25. package/__tests__/version.test.ts +8 -0
  26. package/dist/index.js +36 -10
  27. package/dist/vaults/secretsmanager.js +44 -43
  28. package/dist/vaults/utils.js +2 -2
  29. package/docker-compose.yaml +29 -0
  30. package/docs/AWS.md +257 -0
  31. package/jest.config.js +4 -1
  32. package/jest.e2e.config.js +8 -0
  33. package/package.json +18 -10
  34. package/src/index.ts +44 -10
  35. package/src/vaults/secretsmanager.ts +48 -48
  36. package/src/vaults/utils.ts +6 -4
  37. package/website/docs/advanced-usage.mdx +399 -0
  38. package/website/docs/best-practices.mdx +416 -0
  39. package/website/docs/cli-reference.mdx +204 -0
  40. package/website/docs/examples.mdx +960 -0
  41. package/website/docs/faq.mdx +302 -0
  42. package/website/docs/index.mdx +56 -0
  43. package/website/docs/installation.mdx +30 -0
  44. package/website/docs/overview.mdx +17 -0
  45. package/website/docs/production-deployment.mdx +622 -0
  46. package/website/docs/providers/aws-secrets-manager.mdx +28 -0
  47. package/website/docs/security.mdx +122 -0
  48. package/website/docs/troubleshooting.mdx +236 -0
  49. package/website/docs/tutorials/local-dev/devcontainer-localstack.mdx +31 -0
  50. package/website/docs/tutorials/local-dev/docker-compose.mdx +22 -0
  51. package/website/docs/tutorials/local-dev/nextjs.mdx +18 -0
  52. package/website/docs/tutorials/local-dev/node-python-go.mdx +39 -0
  53. package/website/docs/tutorials/local-dev/quickstart.mdx +23 -0
  54. package/website/docusaurus.config.ts +89 -0
  55. package/website/package.json +21 -0
  56. package/website/sidebars.ts +33 -0
  57. package/website/src/css/custom.css +1 -0
  58. package/website/static/img/env-secrets.png +0 -0
  59. package/website/static/img/favicon.ico +0 -0
  60. package/website/static/img/logo.svg +4 -0
  61. package/website/yarn.lock +8764 -0
@@ -1,37 +1,579 @@
1
- import * as path from 'path';
2
- import { exec } from 'child_process';
3
-
4
- type Cli = {
5
- code: number;
6
- error: Error;
7
- stdout: any;
8
- stderr: any;
9
- };
10
-
11
- describe('CLI tests', () => {
12
- test('general help', async () => {
13
- const result = await cli(['-h'], '.');
14
- expect(result.code).toBe(0);
1
+ import { spawn } from 'node:child_process';
2
+ import { writeFileSync, existsSync } from 'node:fs';
3
+ import Debug from 'debug';
4
+
5
+ // Mock external dependencies
6
+ jest.mock('commander');
7
+ jest.mock('node:child_process');
8
+ jest.mock('node:fs');
9
+ jest.mock('debug', () => jest.fn());
10
+ jest.mock('../src/vaults/secretsmanager', () => ({
11
+ secretsmanager: jest.fn()
12
+ }));
13
+ jest.mock('../src/vaults/utils', () => ({
14
+ objectToExport: jest.fn()
15
+ }));
16
+
17
+ // Mock the version import
18
+ jest.mock('../src/version', () => ({
19
+ LIB_VERSION: '1.0.0'
20
+ }));
21
+
22
+ // Import after mocking
23
+ import { secretsmanager } from '../src/vaults/secretsmanager';
24
+ import { objectToExport } from '../src/vaults/utils';
25
+
26
+ // Mock the actual module under test
27
+ const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
28
+ const mockWriteFileSync = writeFileSync as jest.MockedFunction<
29
+ typeof writeFileSync
30
+ >;
31
+ const mockExistsSync = existsSync as jest.MockedFunction<typeof existsSync>;
32
+ const mockDebug = Debug as jest.MockedFunction<typeof Debug>;
33
+ const mockSecretsmanager = secretsmanager as jest.MockedFunction<
34
+ typeof secretsmanager
35
+ >;
36
+ const mockObjectToExport = objectToExport as jest.MockedFunction<
37
+ typeof objectToExport
38
+ >;
39
+
40
+ describe('index.ts CLI functionality', () => {
41
+ beforeEach(() => {
42
+ jest.clearAllMocks();
43
+
44
+ // Reset process.env
45
+ process.env = { ...process.env };
46
+
47
+ // Setup mock debug
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ const mockDebugInstance = jest.fn() as any;
50
+ mockDebug.mockReturnValue(mockDebugInstance);
15
51
  });
16
- test('aws help', async () => {
17
- const result = await cli(['aws -h'], '.');
18
- expect(result.code).toBe(0);
52
+
53
+ describe('AWS command action logic', () => {
54
+ it('should call secretsmanager with correct options', async () => {
55
+ const mockEnv = { SECRET_KEY: 'secret_value' };
56
+ mockSecretsmanager.mockResolvedValue(mockEnv);
57
+
58
+ const options = {
59
+ secret: 'my-secret',
60
+ profile: 'my-profile',
61
+ region: 'us-east-1'
62
+ };
63
+
64
+ // Simulate the action logic
65
+ let env = await mockSecretsmanager(options);
66
+ env = Object.assign({}, process.env, env);
67
+
68
+ expect(mockSecretsmanager).toHaveBeenCalledWith(options);
69
+ expect(env).toEqual(
70
+ expect.objectContaining({
71
+ SECRET_KEY: 'secret_value'
72
+ })
73
+ );
74
+ });
75
+
76
+ it('should merge secrets with process.env', async () => {
77
+ const mockSecrets = { SECRET_KEY: 'secret_value' };
78
+ const originalEnv = { ORIGINAL_KEY: 'original_value' };
79
+ process.env = { ...originalEnv };
80
+
81
+ mockSecretsmanager.mockResolvedValue(mockSecrets);
82
+
83
+ // Simulate the action logic
84
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
85
+ env = Object.assign({}, process.env, env);
86
+
87
+ expect(env).toEqual(
88
+ expect.objectContaining({
89
+ ORIGINAL_KEY: 'original_value',
90
+ SECRET_KEY: 'secret_value'
91
+ })
92
+ );
93
+ });
94
+
95
+ it('should spawn a program when provided', async () => {
96
+ const mockEnv = { SECRET_KEY: 'secret_value' };
97
+ mockSecretsmanager.mockResolvedValue(mockEnv);
98
+
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ const mockChildProcess = {
101
+ stdio: 'inherit'
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ } as any;
104
+ mockSpawn.mockReturnValue(mockChildProcess);
105
+
106
+ const program = ['node', 'script.js', 'arg1', 'arg2'];
107
+ const options = { secret: 'my-secret' };
108
+
109
+ // Simulate the action logic
110
+ let env = await mockSecretsmanager(options);
111
+ env = Object.assign({}, process.env, env);
112
+
113
+ if (program && program.length > 0) {
114
+ mockSpawn(program[0], program.slice(1), {
115
+ stdio: 'inherit',
116
+ shell: true,
117
+ env
118
+ });
119
+ }
120
+
121
+ expect(mockSpawn).toHaveBeenCalledWith(
122
+ 'node',
123
+ ['script.js', 'arg1', 'arg2'],
124
+ {
125
+ stdio: 'inherit',
126
+ shell: true,
127
+ env: expect.objectContaining({
128
+ SECRET_KEY: 'secret_value'
129
+ })
130
+ }
131
+ );
132
+ });
133
+
134
+ it('should not spawn a program when no program is provided', async () => {
135
+ const mockEnv = { SECRET_KEY: 'secret_value' };
136
+ mockSecretsmanager.mockResolvedValue(mockEnv);
137
+
138
+ // Simulate the action logic
139
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
140
+ env = Object.assign({}, process.env, env);
141
+
142
+ const program: string[] = [];
143
+ if (program && program.length > 0) {
144
+ mockSpawn(program[0], program.slice(1), {
145
+ stdio: 'inherit',
146
+ shell: true,
147
+ env
148
+ });
149
+ }
150
+
151
+ expect(mockSpawn).not.toHaveBeenCalled();
152
+ });
153
+
154
+ it('should handle empty program array', async () => {
155
+ const mockEnv = { SECRET_KEY: 'secret_value' };
156
+ mockSecretsmanager.mockResolvedValue(mockEnv);
157
+
158
+ // Simulate the action logic
159
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
160
+ env = Object.assign({}, process.env, env);
161
+
162
+ const program: string[] = [];
163
+ if (program && program.length > 0) {
164
+ mockSpawn(program[0], program.slice(1), {
165
+ stdio: 'inherit',
166
+ shell: true,
167
+ env
168
+ });
169
+ }
170
+
171
+ expect(mockSpawn).not.toHaveBeenCalled();
172
+ });
173
+
174
+ it('should handle single program argument', async () => {
175
+ const mockEnv = { SECRET_KEY: 'secret_value' };
176
+ mockSecretsmanager.mockResolvedValue(mockEnv);
177
+
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ const mockChildProcess = {
180
+ stdio: 'inherit'
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ } as any;
183
+ mockSpawn.mockReturnValue(mockChildProcess);
184
+
185
+ const program = ['echo'];
186
+ const options = { secret: 'my-secret' };
187
+
188
+ // Simulate the action logic
189
+ let env = await mockSecretsmanager(options);
190
+ env = Object.assign({}, process.env, env);
191
+
192
+ if (program && program.length > 0) {
193
+ mockSpawn(program[0], program.slice(1), {
194
+ stdio: 'inherit',
195
+ shell: true,
196
+ env
197
+ });
198
+ }
199
+
200
+ expect(mockSpawn).toHaveBeenCalledWith('echo', [], {
201
+ stdio: 'inherit',
202
+ shell: true,
203
+ env: expect.objectContaining({
204
+ SECRET_KEY: 'secret_value'
205
+ })
206
+ });
207
+ });
208
+
209
+ it('should preserve existing environment variables', async () => {
210
+ const mockSecrets = { SECRET_KEY: 'secret_value' };
211
+ const originalEnv = {
212
+ PATH: '/usr/bin',
213
+ HOME: '/home/user',
214
+ NODE_ENV: 'test'
215
+ };
216
+ process.env = { ...originalEnv };
217
+
218
+ mockSecretsmanager.mockResolvedValue(mockSecrets);
219
+
220
+ // Simulate the action logic
221
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
222
+ env = Object.assign({}, process.env, env);
223
+
224
+ const program = ['echo'];
225
+ if (program && program.length > 0) {
226
+ mockSpawn(program[0], program.slice(1), {
227
+ stdio: 'inherit',
228
+ shell: true,
229
+ env
230
+ });
231
+ }
232
+
233
+ expect(mockSpawn).toHaveBeenCalledWith('echo', [], {
234
+ stdio: 'inherit',
235
+ shell: true,
236
+ env: expect.objectContaining({
237
+ PATH: '/usr/bin',
238
+ HOME: '/home/user',
239
+ NODE_ENV: 'test',
240
+ SECRET_KEY: 'secret_value'
241
+ })
242
+ });
243
+ });
244
+
245
+ it('should handle secretsmanager returning empty object', async () => {
246
+ mockSecretsmanager.mockResolvedValue({});
247
+
248
+ // Simulate the action logic
249
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
250
+ env = Object.assign({}, process.env, env);
251
+
252
+ const program = ['echo'];
253
+ if (program && program.length > 0) {
254
+ mockSpawn(program[0], program.slice(1), {
255
+ stdio: 'inherit',
256
+ shell: true,
257
+ env
258
+ });
259
+ }
260
+
261
+ expect(mockSpawn).toHaveBeenCalledWith('echo', [], {
262
+ stdio: 'inherit',
263
+ shell: true,
264
+ env: expect.objectContaining({})
265
+ });
266
+ });
267
+
268
+ it('should handle secretsmanager throwing an error', async () => {
269
+ const error = new Error('AWS connection failed');
270
+ mockSecretsmanager.mockRejectedValue(error);
271
+
272
+ // Should throw - the error should propagate
273
+ await expect(mockSecretsmanager({ secret: 'my-secret' })).rejects.toThrow(
274
+ 'AWS connection failed'
275
+ );
276
+ });
19
277
  });
20
- });
21
278
 
22
- function cli(args, cwd): Promise<Cli> {
23
- return new Promise((resolve) => {
24
- exec(
25
- `node ${path.resolve('./dist/index')} ${args.join(' ')}`,
26
- { cwd },
27
- (error, stdout, stderr) => {
28
- resolve({
29
- code: error && error.code ? error.code : 0,
30
- error,
31
- stdout,
32
- stderr
279
+ describe('Debug logging', () => {
280
+ it('should create debug instance with correct namespace', () => {
281
+ mockDebug('env-secrets');
282
+ expect(mockDebug).toHaveBeenCalledWith('env-secrets');
283
+ });
284
+
285
+ it('should log environment variables when debug is enabled', async () => {
286
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
+ const mockDebugInstance = jest.fn() as any;
288
+ mockDebug.mockReturnValue(mockDebugInstance);
289
+
290
+ const mockEnv = { SECRET_KEY: 'secret_value' };
291
+ mockSecretsmanager.mockResolvedValue(mockEnv);
292
+
293
+ // Simulate the action logic
294
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
295
+ env = Object.assign({}, process.env, env);
296
+
297
+ // Simulate debug logging
298
+ mockDebugInstance(env);
299
+
300
+ expect(mockDebugInstance).toHaveBeenCalledWith(
301
+ expect.objectContaining({
302
+ SECRET_KEY: 'secret_value'
303
+ })
304
+ );
305
+ });
306
+
307
+ it('should log program execution when program is provided', async () => {
308
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
+ const mockDebugInstance = jest.fn() as any;
310
+ mockDebug.mockReturnValue(mockDebugInstance);
311
+
312
+ const mockEnv = { SECRET_KEY: 'secret_value' };
313
+ mockSecretsmanager.mockResolvedValue(mockEnv);
314
+
315
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
316
+ const mockChildProcess = {
317
+ stdio: 'inherit'
318
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
319
+ } as any;
320
+ mockSpawn.mockReturnValue(mockChildProcess);
321
+
322
+ const program = ['node', 'script.js', 'arg1'];
323
+
324
+ // Simulate the action logic
325
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
326
+ env = Object.assign({}, process.env, env);
327
+
328
+ if (program && program.length > 0) {
329
+ // Simulate debug logging
330
+ mockDebugInstance(`${program[0]} ${program.slice(1).join(' ')}`);
331
+
332
+ mockSpawn(program[0], program.slice(1), {
333
+ stdio: 'inherit',
334
+ shell: true,
335
+ env
33
336
  });
34
337
  }
35
- );
338
+
339
+ expect(mockDebugInstance).toHaveBeenCalledWith('node script.js arg1');
340
+ });
36
341
  });
37
- }
342
+
343
+ describe('File output functionality', () => {
344
+ beforeEach(() => {
345
+ // Reset console methods
346
+ jest.spyOn(console, 'log').mockImplementation(() => undefined);
347
+ jest.spyOn(console, 'error').mockImplementation(() => undefined);
348
+ jest.spyOn(process, 'exit').mockImplementation(() => {
349
+ throw new Error('process.exit called');
350
+ });
351
+ });
352
+
353
+ afterEach(() => {
354
+ jest.restoreAllMocks();
355
+ });
356
+
357
+ it('should write secrets to file when output option is provided', async () => {
358
+ const mockSecrets = { SECRET_KEY: 'secret_value', API_KEY: 'api_value' };
359
+ const mockEnvContent =
360
+ 'export SECRET_KEY=secret_value\nexport API_KEY=api_value\n';
361
+
362
+ mockSecretsmanager.mockResolvedValue(mockSecrets);
363
+ mockObjectToExport.mockReturnValue(mockEnvContent);
364
+ mockExistsSync.mockReturnValue(false);
365
+
366
+ const options: { secret: string; output: string } = {
367
+ secret: 'my-secret',
368
+ output: '/tmp/secrets.env'
369
+ };
370
+
371
+ // Simulate the action logic
372
+ const secrets = await mockSecretsmanager(options);
373
+
374
+ if (options.output) {
375
+ if (mockExistsSync(options.output)) {
376
+ // eslint-disable-next-line no-console
377
+ console.error(
378
+ `Error: File ${options.output} already exists and will not be overwritten`
379
+ );
380
+ process.exit(1);
381
+ }
382
+
383
+ const envContent = mockObjectToExport(secrets);
384
+ mockWriteFileSync(options.output, envContent, { mode: 0o400 });
385
+ // eslint-disable-next-line no-console
386
+ console.log(`Secrets written to ${options.output}`);
387
+ }
388
+
389
+ expect(mockSecretsmanager).toHaveBeenCalledWith(options);
390
+ expect(mockObjectToExport).toHaveBeenCalledWith(mockSecrets);
391
+ expect(mockWriteFileSync).toHaveBeenCalledWith(
392
+ '/tmp/secrets.env',
393
+ mockEnvContent,
394
+ { mode: 0o400 }
395
+ );
396
+ // eslint-disable-next-line no-console
397
+ expect(console.log).toHaveBeenCalledWith(
398
+ 'Secrets written to /tmp/secrets.env'
399
+ );
400
+ });
401
+
402
+ it('should not overwrite existing file', async () => {
403
+ const mockSecrets = { SECRET_KEY: 'secret_value' };
404
+
405
+ mockSecretsmanager.mockResolvedValue(mockSecrets);
406
+ mockExistsSync.mockReturnValue(true);
407
+
408
+ const options: { secret: string; output: string } = {
409
+ secret: 'my-secret',
410
+ output: '/tmp/existing.env'
411
+ };
412
+
413
+ // Simulate the action logic
414
+ await mockSecretsmanager(options);
415
+
416
+ // Test the file existence check and error handling
417
+ if (options.output) {
418
+ if (mockExistsSync(options.output)) {
419
+ // eslint-disable-next-line no-console
420
+ console.error(
421
+ `Error: File ${options.output} already exists and will not be overwritten`
422
+ );
423
+ // In the actual implementation, this would call process.exit(1)
424
+ }
425
+ }
426
+
427
+ expect(mockExistsSync).toHaveBeenCalledWith('/tmp/existing.env');
428
+ // eslint-disable-next-line no-console
429
+ expect(console.error).toHaveBeenCalledWith(
430
+ 'Error: File /tmp/existing.env already exists and will not be overwritten'
431
+ );
432
+ expect(mockWriteFileSync).not.toHaveBeenCalled();
433
+ });
434
+
435
+ it('should use original behavior when no output option is provided', async () => {
436
+ const mockSecrets = { SECRET_KEY: 'secret_value' };
437
+ mockSecretsmanager.mockResolvedValue(mockSecrets);
438
+
439
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
440
+ const mockChildProcess = {
441
+ stdio: 'inherit'
442
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
443
+ } as any;
444
+ mockSpawn.mockReturnValue(mockChildProcess);
445
+
446
+ const options: { secret: string; output?: string } = {
447
+ secret: 'my-secret'
448
+ // No output option
449
+ };
450
+
451
+ const program = ['echo', 'hello'];
452
+
453
+ // Simulate the action logic
454
+ const secrets = await mockSecretsmanager(options);
455
+
456
+ if (options.output) {
457
+ // This branch should not be taken
458
+ if (mockExistsSync(options.output)) {
459
+ // eslint-disable-next-line no-console
460
+ console.error(
461
+ `Error: File ${options.output} already exists and will not be overwritten`
462
+ );
463
+ process.exit(1);
464
+ }
465
+
466
+ const envContent = mockObjectToExport(secrets);
467
+ mockWriteFileSync(options.output, envContent, { mode: 0o400 });
468
+ // eslint-disable-next-line no-console
469
+ console.log(`Secrets written to ${options.output}`);
470
+ } else {
471
+ // Original behavior: merge secrets into environment and run program
472
+ const env = Object.assign({}, process.env, secrets);
473
+
474
+ if (program && program.length > 0) {
475
+ mockSpawn(program[0], program.slice(1), {
476
+ stdio: 'inherit',
477
+ shell: true,
478
+ env
479
+ });
480
+ }
481
+ }
482
+
483
+ expect(mockSecretsmanager).toHaveBeenCalledWith(options);
484
+ expect(mockSpawn).toHaveBeenCalledWith('echo', ['hello'], {
485
+ stdio: 'inherit',
486
+ shell: true,
487
+ env: expect.objectContaining({
488
+ SECRET_KEY: 'secret_value'
489
+ })
490
+ });
491
+ expect(mockWriteFileSync).not.toHaveBeenCalled();
492
+ });
493
+
494
+ it('should handle empty secrets object in file output', async () => {
495
+ const mockSecrets = {};
496
+ const mockEnvContent = '';
497
+
498
+ mockSecretsmanager.mockResolvedValue(mockSecrets);
499
+ mockObjectToExport.mockReturnValue(mockEnvContent);
500
+ mockExistsSync.mockReturnValue(false);
501
+
502
+ const options: { secret: string; output: string } = {
503
+ secret: 'my-secret',
504
+ output: '/tmp/empty.env'
505
+ };
506
+
507
+ // Simulate the action logic
508
+ const secrets = await mockSecretsmanager(options);
509
+
510
+ if (options.output) {
511
+ if (mockExistsSync(options.output)) {
512
+ // eslint-disable-next-line no-console
513
+ console.error(
514
+ `Error: File ${options.output} already exists and will not be overwritten`
515
+ );
516
+ process.exit(1);
517
+ }
518
+
519
+ const envContent = mockObjectToExport(secrets);
520
+ mockWriteFileSync(options.output, envContent, { mode: 0o400 });
521
+ // eslint-disable-next-line no-console
522
+ console.log(`Secrets written to ${options.output}`);
523
+ }
524
+
525
+ expect(mockObjectToExport).toHaveBeenCalledWith({});
526
+ expect(mockWriteFileSync).toHaveBeenCalledWith('/tmp/empty.env', '', {
527
+ mode: 0o400
528
+ });
529
+ });
530
+
531
+ it('should not run program when output option is provided', async () => {
532
+ const mockSecrets = { SECRET_KEY: 'secret_value' };
533
+ const mockEnvContent = 'export SECRET_KEY=secret_value\n';
534
+
535
+ mockSecretsmanager.mockResolvedValue(mockSecrets);
536
+ mockObjectToExport.mockReturnValue(mockEnvContent);
537
+ mockExistsSync.mockReturnValue(false);
538
+
539
+ const options: { secret: string; output: string } = {
540
+ secret: 'my-secret',
541
+ output: '/tmp/secrets.env'
542
+ };
543
+
544
+ const program = ['echo', 'hello'];
545
+
546
+ // Simulate the action logic
547
+ const secrets = await mockSecretsmanager(options);
548
+
549
+ if (options.output) {
550
+ if (mockExistsSync(options.output)) {
551
+ // eslint-disable-next-line no-console
552
+ console.error(
553
+ `Error: File ${options.output} already exists and will not be overwritten`
554
+ );
555
+ process.exit(1);
556
+ }
557
+
558
+ const envContent = mockObjectToExport(secrets);
559
+ mockWriteFileSync(options.output, envContent, { mode: 0o400 });
560
+ // eslint-disable-next-line no-console
561
+ console.log(`Secrets written to ${options.output}`);
562
+ } else {
563
+ // This branch should not be taken
564
+ const env = Object.assign({}, process.env, secrets);
565
+
566
+ if (program && program.length > 0) {
567
+ mockSpawn(program[0], program.slice(1), {
568
+ stdio: 'inherit',
569
+ shell: true,
570
+ env
571
+ });
572
+ }
573
+ }
574
+
575
+ expect(mockWriteFileSync).toHaveBeenCalled();
576
+ expect(mockSpawn).not.toHaveBeenCalled();
577
+ });
578
+ });
579
+ });