env-secrets 0.3.2 → 0.3.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.
@@ -1,13 +1,34 @@
1
- import {
2
- SecretsManagerClient,
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
- jest.mock('@aws-sdk/client-sts');
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.any(GetCallerIdentityCommand)
87
+ expect.objectContaining({ input: {} })
76
88
  );
77
89
  expect(mockSecretsManagerSend).toHaveBeenCalledWith(
78
- expect.any(GetSecretValueCommand)
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.any(GetCallerIdentityCommand)
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({ region: 'us-west-2' });
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
@@ -18,8 +18,15 @@ const client_sts_1 = require("@aws-sdk/client-sts");
18
18
  const credential_providers_1 = require("@aws-sdk/credential-providers");
19
19
  const debug_1 = __importDefault(require("debug"));
20
20
  const debug = (0, debug_1.default)('env-secrets:secretsmanager');
21
- const checkConnection = (region) => __awaiter(void 0, void 0, void 0, function* () {
22
- const stsClient = new client_sts_1.STSClient({ region });
21
+ const isCredentialsError = (error) => {
22
+ if (!error || typeof error !== 'object') {
23
+ return false;
24
+ }
25
+ const errorName = 'name' in error ? error.name : undefined;
26
+ return (errorName === 'CredentialsError' || errorName === 'CredentialsProviderError');
27
+ };
28
+ const checkConnection = (region, credentials) => __awaiter(void 0, void 0, void 0, function* () {
29
+ const stsClient = new client_sts_1.STSClient({ region, credentials });
23
30
  const command = new client_sts_1.GetCallerIdentityCommand({});
24
31
  try {
25
32
  const data = yield stsClient.send(command);
@@ -27,6 +34,11 @@ const checkConnection = (region) => __awaiter(void 0, void 0, void 0, function*
27
34
  return true;
28
35
  }
29
36
  catch (err) {
37
+ if (isCredentialsError(err) && err.message) {
38
+ // eslint-disable-next-line no-console
39
+ console.error(err.message);
40
+ return false;
41
+ }
30
42
  // eslint-disable-next-line no-console
31
43
  console.error(err);
32
44
  return false;
@@ -55,7 +67,7 @@ const secretsmanager = (options) => __awaiter(void 0, void 0, void 0, function*
55
67
  if (!config.region) {
56
68
  debug('no region set');
57
69
  }
58
- const connected = yield checkConnection(region);
70
+ const connected = yield checkConnection(region, credentials);
59
71
  if (connected) {
60
72
  const client = new client_secrets_manager_1.SecretsManagerClient(config);
61
73
  try {
package/docs/AWS.md CHANGED
@@ -10,7 +10,7 @@ The `env-secrets` tool supports AWS Secrets Manager as a secret vault. It can re
10
10
 
11
11
  - [AWS CLI](https://docs.aws.amazon.com/cli/index.html) installed and configured
12
12
  - AWS credentials with appropriate permissions to access Secrets Manager
13
- - Node.js 18.0.0 or higher
13
+ - Node.js 20.0.0 or higher
14
14
 
15
15
  ## Authentication Methods
16
16
 
@@ -0,0 +1,67 @@
1
+ const globals = require('globals');
2
+ const js = require('@eslint/js');
3
+ const tsParser = require('@typescript-eslint/parser');
4
+ const tsPlugin = require('@typescript-eslint/eslint-plugin');
5
+ const prettierPlugin = require('eslint-plugin-prettier');
6
+ const prettierConfig = require('eslint-config-prettier');
7
+
8
+ const tsEslintRecommendedRules =
9
+ tsPlugin.configs['eslint-recommended']?.overrides?.[0]?.rules ?? {};
10
+
11
+ module.exports = [
12
+ {
13
+ ignores: [
14
+ 'node_modules/**',
15
+ 'dist/**',
16
+ 'website/build/**',
17
+ 'website/node_modules/**',
18
+ 'website/.docusaurus/**',
19
+ 'coverage/**',
20
+ 'coverage-e2e/**'
21
+ ]
22
+ },
23
+ {
24
+ ...js.configs.recommended,
25
+ files: ['**/*.{js,mjs,cjs}'],
26
+ languageOptions: {
27
+ ...(js.configs.recommended.languageOptions ?? {}),
28
+ globals: globals.node
29
+ },
30
+ rules: {
31
+ ...(js.configs.recommended.rules ?? {}),
32
+ 'no-console': 'warn'
33
+ }
34
+ },
35
+ {
36
+ files: ['**/*.ts'],
37
+ languageOptions: {
38
+ parser: tsParser,
39
+ parserOptions: {
40
+ ecmaVersion: 'latest',
41
+ sourceType: 'module'
42
+ },
43
+ globals: {
44
+ ...globals.node,
45
+ ...globals.jest
46
+ }
47
+ },
48
+ plugins: {
49
+ '@typescript-eslint': tsPlugin
50
+ },
51
+ rules: {
52
+ ...(js.configs.recommended.rules ?? {}),
53
+ ...tsEslintRecommendedRules,
54
+ ...(tsPlugin.configs.recommended.rules ?? {}),
55
+ 'no-console': 'warn'
56
+ }
57
+ },
58
+ {
59
+ plugins: {
60
+ prettier: prettierPlugin
61
+ },
62
+ rules: {
63
+ 'prettier/prettier': 'error'
64
+ }
65
+ },
66
+ prettierConfig
67
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "env-secrets",
3
- "version": "0.3.2",
3
+ "version": "0.3.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)",
@@ -16,10 +16,12 @@
16
16
  "build": "rimraf ./dist && tsc -b src",
17
17
  "start": "node dist/index.js",
18
18
  "postbuild": "chmod 755 ./dist/index.js",
19
- "lint": "eslint . --ext .ts,.js",
19
+ "lint": "eslint .",
20
+ "lint:fix": "eslint . --fix",
20
21
  "release": "release-it",
21
- "prettier:fix": "npx prettier --write .",
22
- "prettier:check": "npx prettier --check .",
22
+ "prettier": "prettier . --check",
23
+ "prettier:fix": "prettier . --write",
24
+ "prettier:check": "prettier . --check",
23
25
  "test": "npm run test:unit && npm run test:e2e",
24
26
  "test:unit": "jest __tests__",
25
27
  "test:unit:coverage": "jest __tests__ --coverage",
@@ -27,6 +29,7 @@
27
29
  "test:e2e:debug": "npm run build && DEBUG=true jest --config jest.e2e.config.js"
28
30
  },
29
31
  "devDependencies": {
32
+ "@everydaydevopsio/ballast": "^3.2.1",
30
33
  "@types/debug": "^4.1.12",
31
34
  "@types/jest": "^29.5.14",
32
35
  "@types/node": "^18.19.130",
@@ -36,30 +39,37 @@
36
39
  "eslint": "^8.57.1",
37
40
  "eslint-config-prettier": "^8.10.2",
38
41
  "eslint-plugin-prettier": "^4.2.5",
42
+ "globals": "^16.0.0",
39
43
  "husky": "^8.0.3",
40
44
  "jest": "^29.7.0",
41
45
  "lint-staged": "13.3.0",
42
46
  "prettier": "^2.8.8",
43
47
  "release-it": "^15.11.0",
44
48
  "rimraf": "^3.0.2",
49
+ "tsc-files": "^1.1.4",
45
50
  "ts-jest": "^29.4.6",
46
51
  "ts-node": "^10.9.2",
47
52
  "typescript": "^4.9.5"
48
53
  },
49
54
  "dependencies": {
50
- "@aws-sdk/client-secrets-manager": "^3.899.0",
51
- "@aws-sdk/client-sts": "^3.899.0",
52
- "@aws-sdk/credential-providers": "^3.899.0",
55
+ "@aws-sdk/client-secrets-manager": "^3.990.0",
56
+ "@aws-sdk/client-sts": "^3.990.0",
57
+ "@aws-sdk/credential-providers": "^3.990.0",
53
58
  "commander": "^9.5.0",
54
59
  "debug": "^4.4.3"
55
60
  },
56
61
  "lint-staged": {
57
- "*.{ts,js}": [
58
- "prettier --write .",
59
- "eslint --fix ."
62
+ "**/*.js": [
63
+ "prettier --write",
64
+ "eslint --fix"
60
65
  ],
61
- "*.{json,md,mdx,yaml,yml}": [
62
- "prettier --write ."
66
+ "**/*.ts": [
67
+ "tsc-files --noEmit --project src/tsconfig.json",
68
+ "prettier --write",
69
+ "eslint --fix"
70
+ ],
71
+ "**/*.{json,md,mdx,yaml,yml}": [
72
+ "prettier --write"
63
73
  ]
64
74
  },
65
75
  "publishConfig": {
@@ -70,6 +80,6 @@
70
80
  "env-secrets": "dist/index.js"
71
81
  },
72
82
  "engines": {
73
- "node": ">=18.0.0"
83
+ "node": ">=20.0.0"
74
84
  }
75
85
  }
@@ -14,8 +14,27 @@ interface secretsmanagerType {
14
14
  region?: string;
15
15
  }
16
16
 
17
- const checkConnection = async (region?: string) => {
18
- const stsClient = new STSClient({ region });
17
+ interface AWSLikeError {
18
+ name?: string;
19
+ message?: string;
20
+ }
21
+
22
+ const isCredentialsError = (error: unknown): error is AWSLikeError => {
23
+ if (!error || typeof error !== 'object') {
24
+ return false;
25
+ }
26
+
27
+ const errorName = 'name' in error ? error.name : undefined;
28
+ return (
29
+ errorName === 'CredentialsError' || errorName === 'CredentialsProviderError'
30
+ );
31
+ };
32
+
33
+ const checkConnection = async (
34
+ region?: string,
35
+ credentials?: ReturnType<typeof fromIni>
36
+ ) => {
37
+ const stsClient = new STSClient({ region, credentials });
19
38
  const command = new GetCallerIdentityCommand({});
20
39
 
21
40
  try {
@@ -23,6 +42,12 @@ const checkConnection = async (region?: string) => {
23
42
  debug(data);
24
43
  return true;
25
44
  } catch (err) {
45
+ if (isCredentialsError(err) && err.message) {
46
+ // eslint-disable-next-line no-console
47
+ console.error(err.message);
48
+ return false;
49
+ }
50
+
26
51
  // eslint-disable-next-line no-console
27
52
  console.error(err);
28
53
  return false;
@@ -57,7 +82,7 @@ export const secretsmanager = async (options: secretsmanagerType) => {
57
82
  debug('no region set');
58
83
  }
59
84
 
60
- const connected = await checkConnection(region);
85
+ const connected = await checkConnection(region, credentials);
61
86
 
62
87
  if (connected) {
63
88
  const client = new SecretsManagerClient(config);
@@ -738,7 +738,7 @@ jobs:
738
738
  runs-on: ubuntu-latest
739
739
  steps:
740
740
  - uses: actions/checkout@v3
741
-
741
+
742
742
  - name: Configure AWS credentials
743
743
  uses: aws-actions/configure-aws-credentials@v2
744
744
  with:
@@ -4,7 +4,7 @@ title: Installation
4
4
 
5
5
  ## Requirements
6
6
 
7
- - Node.js 18+
7
+ - Node.js 20.0.0 or higher
8
8
  - (For AWS) AWS credentials via env vars, profile, or IAM role
9
9
 
10
10
  ## Install
package/.eslintignore DELETED
@@ -1,4 +0,0 @@
1
- node_modules/
2
- dist/
3
- website/build/
4
- website/node_modules/
package/.eslintrc DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "root": true,
3
- "parser": "@typescript-eslint/parser",
4
- "plugins": ["@typescript-eslint", "prettier"],
5
- "extends": [
6
- "eslint:recommended",
7
- "plugin:@typescript-eslint/eslint-recommended",
8
- "plugin:@typescript-eslint/recommended",
9
- "prettier"
10
- ],
11
- "env": {
12
- "jest": true
13
- },
14
- "rules": {
15
- "no-console": "warn",
16
- "prettier/prettier": "error"
17
- }
18
- }
package/.lintstagedrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "*.{js,ts}": ["prettier --write", "eslint --fix"],
3
- "*.{json,md,yaml}": ["prettier --write"]
4
- }