env-secrets 0.5.2 → 0.5.3

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.
@@ -34,7 +34,7 @@ jobs:
34
34
  working-directory: website
35
35
  run: yarn build
36
36
  - name: Upload artifact
37
- uses: actions/upload-pages-artifact@v4
37
+ uses: actions/upload-pages-artifact@v5
38
38
  with:
39
39
  path: website/build
40
40
 
@@ -1,4 +1,4 @@
1
- import { cliWithEnv } from './utils/test-utils';
1
+ import { cliWithEnv, cliWithRealSpawn } from './utils/test-utils';
2
2
  import { registerAwsE2eContext } from './utils/aws-e2e-context';
3
3
 
4
4
  describe('AWS Program Execution CLI Args', () => {
@@ -42,3 +42,99 @@ describe('AWS Program Execution CLI Args', () => {
42
42
  expect(envVars.API_KEY).toBe('secret123');
43
43
  });
44
44
  });
45
+
46
+ describe('AWS Real Spawn Execution (no NODE_ENV=test)', () => {
47
+ const { createTestSecret, getLocalStackEnv } = registerAwsE2eContext();
48
+
49
+ test('injected env vars are visible to the spawned child process (shell mode)', async () => {
50
+ const secret = await createTestSecret({
51
+ name: `test-secret-realspawn-${Date.now()}`,
52
+ value: '{"INJECTED_KEY": "injected_value"}',
53
+ description: 'Real spawn env injection test'
54
+ });
55
+
56
+ // printenv avoids any shell-quoting complexity in the test command string
57
+ const result = await cliWithRealSpawn(
58
+ ['aws', '-s', secret.prefixedName, '--', 'printenv', 'INJECTED_KEY'],
59
+ getLocalStackEnv()
60
+ );
61
+
62
+ expect(result.code).toBe(0);
63
+ expect(result.stdout.trim()).toBe('injected_value');
64
+ });
65
+
66
+ test('exit code of child process is propagated on success', async () => {
67
+ const secret = await createTestSecret({
68
+ name: `test-secret-exitcode-ok-${Date.now()}`,
69
+ value: '{"DUMMY": "1"}',
70
+ description: 'Exit code propagation test (success)'
71
+ });
72
+
73
+ // Use --no-shell so node -e args are passed directly without shell re-parsing
74
+ const result = await cliWithRealSpawn(
75
+ [
76
+ 'aws',
77
+ '-s',
78
+ secret.prefixedName,
79
+ '--no-shell',
80
+ '--',
81
+ 'node',
82
+ '-e',
83
+ 'process.exit(0)'
84
+ ],
85
+ getLocalStackEnv()
86
+ );
87
+
88
+ expect(result.code).toBe(0);
89
+ });
90
+
91
+ test('exit code of child process is propagated on failure', async () => {
92
+ const secret = await createTestSecret({
93
+ name: `test-secret-exitcode-fail-${Date.now()}`,
94
+ value: '{"DUMMY": "1"}',
95
+ description: 'Exit code propagation test (failure)'
96
+ });
97
+
98
+ // Use --no-shell so node -e args are passed directly without shell re-parsing
99
+ const result = await cliWithRealSpawn(
100
+ [
101
+ 'aws',
102
+ '-s',
103
+ secret.prefixedName,
104
+ '--no-shell',
105
+ '--',
106
+ 'node',
107
+ '-e',
108
+ 'process.exit(42)'
109
+ ],
110
+ getLocalStackEnv()
111
+ );
112
+
113
+ expect(result.code).toBe(42);
114
+ });
115
+
116
+ test('--no-shell passes args directly and env is injected', async () => {
117
+ const secret = await createTestSecret({
118
+ name: `test-secret-noshell-${Date.now()}`,
119
+ value: '{"INJECTED_KEY": "direct_value"}',
120
+ description: 'No-shell spawn test'
121
+ });
122
+
123
+ // printenv avoids any shell-quoting complexity in the test command string
124
+ const result = await cliWithRealSpawn(
125
+ [
126
+ 'aws',
127
+ '-s',
128
+ secret.prefixedName,
129
+ '--no-shell',
130
+ '--',
131
+ 'printenv',
132
+ 'INJECTED_KEY'
133
+ ],
134
+ getLocalStackEnv()
135
+ );
136
+
137
+ expect(result.code).toBe(0);
138
+ expect(result.stdout.trim()).toBe('direct_value');
139
+ });
140
+ });
@@ -654,6 +654,84 @@ export function restoreTestProfile(
654
654
  }
655
655
  }
656
656
 
657
+ /**
658
+ * Like cliWithEnv but does NOT set NODE_ENV=test, so the real spawn path in
659
+ * src/index.ts is exercised instead of the early-return test branch.
660
+ */
661
+ export async function cliWithRealSpawn(
662
+ args: string[],
663
+ env: Record<string, string>,
664
+ cwd = '.'
665
+ ): Promise<CliResult> {
666
+ return new Promise((resolve) => {
667
+ const cleanEnv = { ...process.env };
668
+ delete cleanEnv.AWS_PROFILE;
669
+ delete cleanEnv.AWS_DEFAULT_PROFILE;
670
+ delete cleanEnv.AWS_SESSION_TOKEN;
671
+ delete cleanEnv.AWS_SECURITY_TOKEN;
672
+ delete cleanEnv.AWS_ROLE_ARN;
673
+ delete cleanEnv.AWS_ROLE_SESSION_NAME;
674
+ delete cleanEnv.AWS_WEB_IDENTITY_TOKEN_FILE;
675
+ delete cleanEnv.AWS_WEB_IDENTITY_TOKEN;
676
+ // Deliberately omit NODE_ENV=test so real spawn is used
677
+ delete cleanEnv.NODE_ENV;
678
+
679
+ const defaultEnv = {
680
+ AWS_ENDPOINT_URL: process.env.LOCALSTACK_URL || 'http://localhost:4566',
681
+ AWS_ACCESS_KEY_ID: 'test',
682
+ AWS_SECRET_ACCESS_KEY: 'test',
683
+ AWS_DEFAULT_REGION: 'us-east-1',
684
+ AWS_REGION: 'us-east-1'
685
+ };
686
+
687
+ const envVars = { ...cleanEnv, ...defaultEnv, ...env };
688
+ const cliPath = path.resolve('./dist/index');
689
+ const spawnArgs = [cliPath, ...args];
690
+ const command = `node ${cliPath} ${args.join(' ')}`;
691
+
692
+ debugLog(`Running CLI command (real spawn): ${command}`);
693
+
694
+ const child = spawn('node', spawnArgs, { cwd, env: envVars });
695
+
696
+ let stdout = '';
697
+ let stderr = '';
698
+ child.stdout.on('data', (chunk: Buffer) => {
699
+ stdout += chunk.toString();
700
+ });
701
+ child.stderr.on('data', (chunk: Buffer) => {
702
+ stderr += chunk.toString();
703
+ });
704
+
705
+ child.on('close', (code: number | null, signal: NodeJS.Signals | null) => {
706
+ const exitCode = code ?? (signal ? 1 : 0);
707
+ const errorMessage = signal
708
+ ? `Process terminated by signal ${signal}`
709
+ : exitCode !== 0
710
+ ? `Process exited with code ${exitCode}`
711
+ : null;
712
+ const result = {
713
+ code: exitCode,
714
+ error: errorMessage ? new Error(errorMessage) : null,
715
+ stdout,
716
+ stderr
717
+ };
718
+
719
+ if (exitCode !== 0) {
720
+ debugError(
721
+ signal
722
+ ? `CLI command failed: terminated by signal ${signal}`
723
+ : `CLI command failed with code ${exitCode}`
724
+ );
725
+ debugError(`Command: ${command}`);
726
+ debugError(`Stdout: ${result.stdout}`);
727
+ debugError(`Stderr: ${result.stderr}`);
728
+ }
729
+
730
+ resolve(result);
731
+ });
732
+ });
733
+ }
734
+
657
735
  export async function checkAwslocalInstalled(): Promise<void> {
658
736
  try {
659
737
  await execAwslocalCommand('awslocal --version', {});
@@ -39,6 +39,24 @@ const mockObjectToExport = objectToExport as jest.MockedFunction<
39
39
  typeof objectToExport
40
40
  >;
41
41
 
42
+ // Build a ChildProcess-like mock that records event handlers
43
+ function makeChildMock() {
44
+ const handlers: Record<string, ((...args: unknown[]) => void)[]> = {};
45
+ const child = {
46
+ stdio: 'inherit',
47
+ on: jest.fn((event: string, cb: (...args: unknown[]) => void) => {
48
+ if (!handlers[event]) handlers[event] = [];
49
+ handlers[event].push(cb);
50
+ return child;
51
+ }),
52
+ emit(event: string, ...args: unknown[]) {
53
+ (handlers[event] ?? []).forEach((cb) => cb(...args));
54
+ }
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ } as any;
57
+ return child;
58
+ }
59
+
42
60
  describe('index.ts CLI functionality', () => {
43
61
  beforeEach(() => {
44
62
  jest.clearAllMocks();
@@ -94,43 +112,67 @@ describe('index.ts CLI functionality', () => {
94
112
  );
95
113
  });
96
114
 
97
- it('should spawn a program when provided', async () => {
115
+ it('should spawn using shell (default) with joined command string', async () => {
98
116
  const mockEnv = { SECRET_KEY: 'secret_value' };
99
117
  mockSecretsmanager.mockResolvedValue(mockEnv);
100
118
 
101
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
- const mockChildProcess = {
103
- stdio: 'inherit'
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
- } as any;
106
- mockSpawn.mockReturnValue(mockChildProcess);
119
+ const child = makeChildMock();
120
+ mockSpawn.mockReturnValue(child);
107
121
 
108
122
  const program = ['node', 'script.js', 'arg1', 'arg2'];
109
- const options = { secret: 'my-secret' };
123
+ const options = { secret: 'my-secret', shell: true };
110
124
 
111
- // Simulate the action logic
112
125
  let env = await mockSecretsmanager(options);
113
126
  env = Object.assign({}, process.env, env);
114
127
 
115
128
  if (program && program.length > 0) {
116
- mockSpawn(program[0], program.slice(1), {
129
+ mockSpawn(program.join(' '), [], {
117
130
  stdio: 'inherit',
118
131
  shell: true,
119
132
  env
120
133
  });
121
134
  }
122
135
 
123
- expect(mockSpawn).toHaveBeenCalledWith(
124
- 'node',
125
- ['script.js', 'arg1', 'arg2'],
126
- {
127
- stdio: 'inherit',
128
- shell: true,
129
- env: expect.objectContaining({
130
- SECRET_KEY: 'secret_value'
131
- })
136
+ expect(mockSpawn).toHaveBeenCalledWith('node script.js arg1 arg2', [], {
137
+ stdio: 'inherit',
138
+ shell: true,
139
+ env: expect.objectContaining({
140
+ SECRET_KEY: 'secret_value'
141
+ })
142
+ });
143
+ });
144
+
145
+ it('should spawn without shell when --no-shell is passed', async () => {
146
+ const mockEnv = { SECRET_KEY: 'secret_value' };
147
+ mockSecretsmanager.mockResolvedValue(mockEnv);
148
+
149
+ const child = makeChildMock();
150
+ mockSpawn.mockReturnValue(child);
151
+
152
+ const program = ['node', 'script.js', 'arg1'];
153
+ const options = { secret: 'my-secret', shell: false };
154
+
155
+ let env = await mockSecretsmanager(options);
156
+ env = Object.assign({}, process.env, env);
157
+
158
+ if (program && program.length > 0) {
159
+ if (options.shell) {
160
+ mockSpawn(program.join(' '), [], {
161
+ stdio: 'inherit',
162
+ shell: true,
163
+ env
164
+ });
165
+ } else {
166
+ mockSpawn(program[0], program.slice(1), { stdio: 'inherit', env });
132
167
  }
133
- );
168
+ }
169
+
170
+ expect(mockSpawn).toHaveBeenCalledWith('node', ['script.js', 'arg1'], {
171
+ stdio: 'inherit',
172
+ env: expect.objectContaining({
173
+ SECRET_KEY: 'secret_value'
174
+ })
175
+ });
134
176
  });
135
177
 
136
178
  it('should not spawn a program when no program is provided', async () => {
@@ -143,7 +185,7 @@ describe('index.ts CLI functionality', () => {
143
185
 
144
186
  const program: string[] = [];
145
187
  if (program && program.length > 0) {
146
- mockSpawn(program[0], program.slice(1), {
188
+ mockSpawn(program.join(' '), [], {
147
189
  stdio: 'inherit',
148
190
  shell: true,
149
191
  env
@@ -163,7 +205,7 @@ describe('index.ts CLI functionality', () => {
163
205
 
164
206
  const program: string[] = [];
165
207
  if (program && program.length > 0) {
166
- mockSpawn(program[0], program.slice(1), {
208
+ mockSpawn(program.join(' '), [], {
167
209
  stdio: 'inherit',
168
210
  shell: true,
169
211
  env
@@ -173,26 +215,21 @@ describe('index.ts CLI functionality', () => {
173
215
  expect(mockSpawn).not.toHaveBeenCalled();
174
216
  });
175
217
 
176
- it('should handle single program argument', async () => {
218
+ it('should handle single program argument (shell mode)', async () => {
177
219
  const mockEnv = { SECRET_KEY: 'secret_value' };
178
220
  mockSecretsmanager.mockResolvedValue(mockEnv);
179
221
 
180
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
181
- const mockChildProcess = {
182
- stdio: 'inherit'
183
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
- } as any;
185
- mockSpawn.mockReturnValue(mockChildProcess);
222
+ const child = makeChildMock();
223
+ mockSpawn.mockReturnValue(child);
186
224
 
187
225
  const program = ['echo'];
188
- const options = { secret: 'my-secret' };
226
+ const options = { secret: 'my-secret', shell: true };
189
227
 
190
- // Simulate the action logic
191
228
  let env = await mockSecretsmanager(options);
192
229
  env = Object.assign({}, process.env, env);
193
230
 
194
231
  if (program && program.length > 0) {
195
- mockSpawn(program[0], program.slice(1), {
232
+ mockSpawn(program.join(' '), [], {
196
233
  stdio: 'inherit',
197
234
  shell: true,
198
235
  env
@@ -219,13 +256,12 @@ describe('index.ts CLI functionality', () => {
219
256
 
220
257
  mockSecretsmanager.mockResolvedValue(mockSecrets);
221
258
 
222
- // Simulate the action logic
223
259
  let env = await mockSecretsmanager({ secret: 'my-secret' });
224
260
  env = Object.assign({}, process.env, env);
225
261
 
226
262
  const program = ['echo'];
227
263
  if (program && program.length > 0) {
228
- mockSpawn(program[0], program.slice(1), {
264
+ mockSpawn(program.join(' '), [], {
229
265
  stdio: 'inherit',
230
266
  shell: true,
231
267
  env
@@ -247,13 +283,12 @@ describe('index.ts CLI functionality', () => {
247
283
  it('should handle secretsmanager returning empty object', async () => {
248
284
  mockSecretsmanager.mockResolvedValue({});
249
285
 
250
- // Simulate the action logic
251
286
  let env = await mockSecretsmanager({ secret: 'my-secret' });
252
287
  env = Object.assign({}, process.env, env);
253
288
 
254
289
  const program = ['echo'];
255
290
  if (program && program.length > 0) {
256
- mockSpawn(program[0], program.slice(1), {
291
+ mockSpawn(program.join(' '), [], {
257
292
  stdio: 'inherit',
258
293
  shell: true,
259
294
  env
@@ -276,6 +311,128 @@ describe('index.ts CLI functionality', () => {
276
311
  'AWS connection failed'
277
312
  );
278
313
  });
314
+
315
+ describe('ChildProcess error and exit handling', () => {
316
+ beforeEach(() => {
317
+ jest.spyOn(process, 'exit').mockImplementation(() => {
318
+ throw new Error('process.exit called');
319
+ });
320
+ jest.spyOn(console, 'error').mockImplementation(() => undefined);
321
+ });
322
+
323
+ afterEach(() => {
324
+ jest.restoreAllMocks();
325
+ });
326
+
327
+ it('should print error message and exit 1 on child process error', async () => {
328
+ const mockEnv = { SECRET_KEY: 'secret_value' };
329
+ mockSecretsmanager.mockResolvedValue(mockEnv);
330
+
331
+ const child = makeChildMock();
332
+ mockSpawn.mockReturnValue(child);
333
+
334
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
335
+ env = Object.assign({}, process.env, env);
336
+
337
+ const program = ['nonexistent-cmd'];
338
+ mockSpawn(program.join(' '), [], {
339
+ stdio: 'inherit',
340
+ shell: true,
341
+ env
342
+ });
343
+
344
+ // Simulate attaching handlers as src/index.ts does
345
+ child.on('error', (err: Error) => {
346
+ // eslint-disable-next-line no-console
347
+ console.error(`Failed to start process: ${err.message}`);
348
+ process.exit(1);
349
+ });
350
+
351
+ expect(() => child.emit('error', new Error('spawn ENOENT'))).toThrow(
352
+ 'process.exit called'
353
+ );
354
+
355
+ // eslint-disable-next-line no-console
356
+ expect(console.error).toHaveBeenCalledWith(
357
+ 'Failed to start process: spawn ENOENT'
358
+ );
359
+ expect(process.exit).toHaveBeenCalledWith(1);
360
+ });
361
+
362
+ it('should propagate child exit code 0', async () => {
363
+ const mockEnv = { SECRET_KEY: 'secret_value' };
364
+ mockSecretsmanager.mockResolvedValue(mockEnv);
365
+
366
+ const child = makeChildMock();
367
+ mockSpawn.mockReturnValue(child);
368
+
369
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
370
+ env = Object.assign({}, process.env, env);
371
+
372
+ mockSpawn(['node', '-e', '"process.exit(0)"'].join(' '), [], {
373
+ stdio: 'inherit',
374
+ shell: true,
375
+ env
376
+ });
377
+
378
+ child.on('exit', (code: number | null, signal: string | null) => {
379
+ process.exit(signal ? 1 : code ?? 0);
380
+ });
381
+
382
+ expect(() => child.emit('exit', 0, null)).toThrow(
383
+ 'process.exit called'
384
+ );
385
+ expect(process.exit).toHaveBeenCalledWith(0);
386
+ });
387
+
388
+ it('should propagate non-zero child exit code', async () => {
389
+ const mockEnv = { SECRET_KEY: 'secret_value' };
390
+ mockSecretsmanager.mockResolvedValue(mockEnv);
391
+
392
+ const child = makeChildMock();
393
+ mockSpawn.mockReturnValue(child);
394
+
395
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
396
+ env = Object.assign({}, process.env, env);
397
+
398
+ mockSpawn(['node', '-e', '"process.exit(42)"'].join(' '), [], {
399
+ stdio: 'inherit',
400
+ shell: true,
401
+ env
402
+ });
403
+
404
+ child.on('exit', (code: number | null, signal: string | null) => {
405
+ process.exit(signal ? 1 : code ?? 0);
406
+ });
407
+
408
+ expect(() => child.emit('exit', 42, null)).toThrow(
409
+ 'process.exit called'
410
+ );
411
+ expect(process.exit).toHaveBeenCalledWith(42);
412
+ });
413
+
414
+ it('should exit 1 when child is killed by a signal', async () => {
415
+ const mockEnv = { SECRET_KEY: 'secret_value' };
416
+ mockSecretsmanager.mockResolvedValue(mockEnv);
417
+
418
+ const child = makeChildMock();
419
+ mockSpawn.mockReturnValue(child);
420
+
421
+ let env = await mockSecretsmanager({ secret: 'my-secret' });
422
+ env = Object.assign({}, process.env, env);
423
+
424
+ mockSpawn('sleep 10', [], { stdio: 'inherit', shell: true, env });
425
+
426
+ child.on('exit', (code: number | null, signal: string | null) => {
427
+ process.exit(signal ? 1 : code ?? 0);
428
+ });
429
+
430
+ expect(() => child.emit('exit', null, 'SIGTERM')).toThrow(
431
+ 'process.exit called'
432
+ );
433
+ expect(process.exit).toHaveBeenCalledWith(1);
434
+ });
435
+ });
279
436
  });
280
437
 
281
438
  describe('Debug logging', () => {
@@ -314,12 +471,8 @@ describe('index.ts CLI functionality', () => {
314
471
  const mockEnv = { SECRET_KEY: 'secret_value' };
315
472
  mockSecretsmanager.mockResolvedValue(mockEnv);
316
473
 
317
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
- const mockChildProcess = {
319
- stdio: 'inherit'
320
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
321
- } as any;
322
- mockSpawn.mockReturnValue(mockChildProcess);
474
+ const child = makeChildMock();
475
+ mockSpawn.mockReturnValue(child);
323
476
 
324
477
  const program = ['node', 'script.js', 'arg1'];
325
478
 
@@ -331,7 +484,7 @@ describe('index.ts CLI functionality', () => {
331
484
  // Simulate debug logging
332
485
  mockDebugInstance(`${program[0]} ${program.slice(1).join(' ')}`);
333
486
 
334
- mockSpawn(program[0], program.slice(1), {
487
+ mockSpawn(program.join(' '), [], {
335
488
  stdio: 'inherit',
336
489
  shell: true,
337
490
  env
@@ -438,16 +591,12 @@ describe('index.ts CLI functionality', () => {
438
591
  const mockSecrets = { SECRET_KEY: 'secret_value' };
439
592
  mockSecretsmanager.mockResolvedValue(mockSecrets);
440
593
 
441
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
442
- const mockChildProcess = {
443
- stdio: 'inherit'
444
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
445
- } as any;
446
- mockSpawn.mockReturnValue(mockChildProcess);
447
-
448
- const options: { secret: string; output?: string } = {
449
- secret: 'my-secret'
450
- // No output option
594
+ const child = makeChildMock();
595
+ mockSpawn.mockReturnValue(child);
596
+
597
+ const options: { secret: string; output?: string; shell: boolean } = {
598
+ secret: 'my-secret',
599
+ shell: true
451
600
  };
452
601
 
453
602
  const program = ['echo', 'hello'];
@@ -474,7 +623,7 @@ describe('index.ts CLI functionality', () => {
474
623
  const env = Object.assign({}, process.env, secrets);
475
624
 
476
625
  if (program && program.length > 0) {
477
- mockSpawn(program[0], program.slice(1), {
626
+ mockSpawn(program.join(' '), [], {
478
627
  stdio: 'inherit',
479
628
  shell: true,
480
629
  env
@@ -483,7 +632,7 @@ describe('index.ts CLI functionality', () => {
483
632
  }
484
633
 
485
634
  expect(mockSecretsmanager).toHaveBeenCalledWith(options);
486
- expect(mockSpawn).toHaveBeenCalledWith('echo', ['hello'], {
635
+ expect(mockSpawn).toHaveBeenCalledWith('echo hello', [], {
487
636
  stdio: 'inherit',
488
637
  shell: true,
489
638
  env: expect.objectContaining({
@@ -538,9 +687,10 @@ describe('index.ts CLI functionality', () => {
538
687
  mockObjectToExport.mockReturnValue(mockEnvContent);
539
688
  mockExistsSync.mockReturnValue(false);
540
689
 
541
- const options: { secret: string; output: string } = {
690
+ const options: { secret: string; output: string; shell: boolean } = {
542
691
  secret: 'my-secret',
543
- output: '/tmp/secrets.env'
692
+ output: '/tmp/secrets.env',
693
+ shell: true
544
694
  };
545
695
 
546
696
  const program = ['echo', 'hello'];
@@ -566,7 +716,7 @@ describe('index.ts CLI functionality', () => {
566
716
  const env = Object.assign({}, process.env, secrets);
567
717
 
568
718
  if (program && program.length > 0) {
569
- mockSpawn(program[0], program.slice(1), {
719
+ mockSpawn(program.join(' '), [], {
570
720
  stdio: 'inherit',
571
721
  shell: true,
572
722
  env
package/dist/index.js CHANGED
@@ -91,6 +91,7 @@ const awsCommand = program
91
91
  .option('-p, --profile <profile>', 'profile to use')
92
92
  .option('-r, --region <region>', 'region to use')
93
93
  .option('-o, --output <file>', 'output secrets to file instead of environment variables')
94
+ .option('--no-shell', 'run program directly without a shell (disables shell expansion)')
94
95
  .action((program, options) => __awaiter(void 0, void 0, void 0, function* () {
95
96
  if (!options.secret) {
96
97
  exitWithError(new Error('Missing required option --secret for this command.'));
@@ -123,10 +124,20 @@ const awsCommand = program
123
124
  console.log(JSON.stringify(env));
124
125
  return;
125
126
  }
126
- (0, node_child_process_1.spawn)(program[0], program.slice(1), {
127
- stdio: 'inherit',
128
- shell: true,
129
- env
127
+ const child = options.shell
128
+ ? (0, node_child_process_1.spawn)(program.join(' '), [], {
129
+ stdio: 'inherit',
130
+ shell: true,
131
+ env
132
+ })
133
+ : (0, node_child_process_1.spawn)(program[0], program.slice(1), { stdio: 'inherit', env });
134
+ child.on('error', (err) => {
135
+ // eslint-disable-next-line no-console
136
+ console.error(`Failed to start process: ${err.message}`);
137
+ process.exit(1);
138
+ });
139
+ child.on('exit', (code, signal) => {
140
+ process.exit(signal ? 1 : code !== null && code !== void 0 ? code : 0);
130
141
  });
131
142
  }
132
143
  }
@@ -1,7 +1,7 @@
1
1
  services:
2
2
  localstack:
3
3
  container_name: 'localstack'
4
- image: localstack/localstack:latest
4
+ image: localstack/localstack:3
5
5
  ports:
6
6
  - '4566:4566' # LocalStack Gateway
7
7
  environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "env-secrets",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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)",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "devDependencies": {
33
33
  "@everydaydevopsio/ballast": "^3.2.1",
34
- "@types/debug": "^4.1.12",
34
+ "@types/debug": "^4.1.13",
35
35
  "@types/jest": "^29.5.14",
36
36
  "@types/node": "^18.19.130",
37
37
  "@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -53,9 +53,9 @@
53
53
  "typescript": "^4.9.5"
54
54
  },
55
55
  "dependencies": {
56
- "@aws-sdk/client-secrets-manager": "^3.1004.0",
57
- "@aws-sdk/client-sts": "^3.1004.0",
58
- "@aws-sdk/credential-providers": "^3.1004.0",
56
+ "@aws-sdk/client-secrets-manager": "^3.1030.0",
57
+ "@aws-sdk/client-sts": "^3.1030.0",
58
+ "@aws-sdk/credential-providers": "^3.1030.0",
59
59
  "commander": "^9.5.0",
60
60
  "debug": "^4.4.3"
61
61
  },
package/src/index.ts CHANGED
@@ -121,6 +121,10 @@ const awsCommand = program
121
121
  '-o, --output <file>',
122
122
  'output secrets to file instead of environment variables'
123
123
  )
124
+ .option(
125
+ '--no-shell',
126
+ 'run program directly without a shell (disables shell expansion)'
127
+ )
124
128
  .action(async (program, options) => {
125
129
  if (!options.secret) {
126
130
  exitWithError(
@@ -163,10 +167,22 @@ const awsCommand = program
163
167
  return;
164
168
  }
165
169
 
166
- spawn(program[0], program.slice(1), {
167
- stdio: 'inherit',
168
- shell: true,
169
- env
170
+ const child = options.shell
171
+ ? spawn(program.join(' '), [], {
172
+ stdio: 'inherit',
173
+ shell: true,
174
+ env
175
+ })
176
+ : spawn(program[0], program.slice(1), { stdio: 'inherit', env });
177
+
178
+ child.on('error', (err) => {
179
+ // eslint-disable-next-line no-console
180
+ console.error(`Failed to start process: ${err.message}`);
181
+ process.exit(1);
182
+ });
183
+
184
+ child.on('exit', (code, signal) => {
185
+ process.exit(signal ? 1 : code ?? 0);
170
186
  });
171
187
  }
172
188
  }