@geekmidas/envkit 0.2.0 → 0.4.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 (73) hide show
  1. package/dist/{EnvironmentBuilder-DfmYRBm-.mjs → EnvironmentBuilder-BSuHZm0y.mjs} +2 -4
  2. package/dist/EnvironmentBuilder-BSuHZm0y.mjs.map +1 -0
  3. package/dist/EnvironmentBuilder-DHfDXJUm.d.mts.map +1 -0
  4. package/dist/{EnvironmentBuilder-W2wku49g.cjs → EnvironmentBuilder-Djr1VsWM.cjs} +2 -4
  5. package/dist/EnvironmentBuilder-Djr1VsWM.cjs.map +1 -0
  6. package/dist/EnvironmentBuilder-Xuf2Dd9u.d.cts.map +1 -0
  7. package/dist/EnvironmentBuilder.cjs +1 -1
  8. package/dist/EnvironmentBuilder.mjs +1 -1
  9. package/dist/EnvironmentParser-Bt246UeP.cjs.map +1 -1
  10. package/dist/{EnvironmentParser-CVWU1ooT.d.mts → EnvironmentParser-CY8TosTN.d.mts} +2 -1
  11. package/dist/EnvironmentParser-CY8TosTN.d.mts.map +1 -0
  12. package/dist/{EnvironmentParser-tV-JjCg7.d.cts → EnvironmentParser-DtOL86NU.d.cts} +2 -1
  13. package/dist/EnvironmentParser-DtOL86NU.d.cts.map +1 -0
  14. package/dist/EnvironmentParser-c06agx31.mjs.map +1 -1
  15. package/dist/EnvironmentParser.d.cts +1 -1
  16. package/dist/EnvironmentParser.d.mts +1 -1
  17. package/dist/SnifferEnvironmentParser.cjs.map +1 -1
  18. package/dist/SnifferEnvironmentParser.d.cts +3 -2
  19. package/dist/SnifferEnvironmentParser.d.cts.map +1 -0
  20. package/dist/SnifferEnvironmentParser.d.mts +3 -2
  21. package/dist/SnifferEnvironmentParser.d.mts.map +1 -0
  22. package/dist/SnifferEnvironmentParser.mjs.map +1 -1
  23. package/dist/{SstEnvironmentBuilder-DEa3lTUB.mjs → SstEnvironmentBuilder-BEBFSUYr.mjs} +2 -2
  24. package/dist/SstEnvironmentBuilder-BEBFSUYr.mjs.map +1 -0
  25. package/dist/SstEnvironmentBuilder-CjURMGjW.d.mts.map +1 -0
  26. package/dist/SstEnvironmentBuilder-D4oSo_KX.d.cts.map +1 -0
  27. package/dist/{SstEnvironmentBuilder-BuFw1hCe.cjs → SstEnvironmentBuilder-wFnN2M5O.cjs} +2 -2
  28. package/dist/SstEnvironmentBuilder-wFnN2M5O.cjs.map +1 -0
  29. package/dist/SstEnvironmentBuilder.cjs +2 -2
  30. package/dist/SstEnvironmentBuilder.mjs +2 -2
  31. package/dist/credentials.cjs +66 -0
  32. package/dist/credentials.cjs.map +1 -0
  33. package/dist/credentials.d.cts +31 -0
  34. package/dist/credentials.d.cts.map +1 -0
  35. package/dist/credentials.d.mts +31 -0
  36. package/dist/credentials.d.mts.map +1 -0
  37. package/dist/credentials.mjs +62 -0
  38. package/dist/credentials.mjs.map +1 -0
  39. package/dist/index.cjs +1 -1
  40. package/dist/index.d.cts +1 -1
  41. package/dist/index.d.mts +1 -1
  42. package/dist/index.mjs +1 -1
  43. package/dist/sst.cjs +2 -2
  44. package/dist/sst.cjs.map +1 -1
  45. package/dist/sst.d.cts +1 -0
  46. package/dist/sst.d.cts.map +1 -0
  47. package/dist/sst.d.mts +1 -0
  48. package/dist/sst.d.mts.map +1 -0
  49. package/dist/sst.mjs +2 -2
  50. package/dist/sst.mjs.map +1 -1
  51. package/examples/basic-usage.ts +329 -333
  52. package/package.json +6 -1
  53. package/src/EnvironmentBuilder.ts +76 -80
  54. package/src/EnvironmentParser.ts +231 -231
  55. package/src/SnifferEnvironmentParser.ts +178 -178
  56. package/src/SstEnvironmentBuilder.ts +127 -127
  57. package/src/__tests__/ConfigParser.spec.ts +388 -388
  58. package/src/__tests__/EnvironmentBuilder.spec.ts +245 -265
  59. package/src/__tests__/EnvironmentParser.spec.ts +828 -828
  60. package/src/__tests__/SnifferEnvironmentParser.spec.ts +380 -326
  61. package/src/__tests__/SstEnvironmentBuilder.spec.ts +347 -367
  62. package/src/__tests__/credentials.integration.spec.ts +239 -0
  63. package/src/__tests__/credentials.spec.ts +136 -0
  64. package/src/__tests__/sst.spec.ts +390 -413
  65. package/src/credentials.ts +99 -0
  66. package/src/index.ts +11 -11
  67. package/src/sst.ts +24 -24
  68. package/sst-env.d.ts +0 -1
  69. package/tsconfig.json +9 -0
  70. package/dist/EnvironmentBuilder-DfmYRBm-.mjs.map +0 -1
  71. package/dist/EnvironmentBuilder-W2wku49g.cjs.map +0 -1
  72. package/dist/SstEnvironmentBuilder-BuFw1hCe.cjs.map +0 -1
  73. package/dist/SstEnvironmentBuilder-DEa3lTUB.mjs.map +0 -1
@@ -0,0 +1,239 @@
1
+ import { createCipheriv, randomBytes } from 'node:crypto';
2
+ import vm from 'node:vm';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ /**
6
+ * Helper to encrypt secrets (mirrors CLI encryption logic)
7
+ */
8
+ function encryptSecrets(secrets: Record<string, string>) {
9
+ const masterKey = randomBytes(32);
10
+ const iv = randomBytes(12);
11
+ const plaintext = JSON.stringify(secrets);
12
+
13
+ const cipher = createCipheriv('aes-256-gcm', masterKey, iv);
14
+ const ciphertext = Buffer.concat([
15
+ cipher.update(plaintext, 'utf-8'),
16
+ cipher.final(),
17
+ ]);
18
+ const authTag = cipher.getAuthTag();
19
+ const combined = Buffer.concat([ciphertext, authTag]);
20
+
21
+ return {
22
+ encrypted: combined.toString('base64'),
23
+ iv: iv.toString('hex'),
24
+ masterKey: masterKey.toString('hex'),
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Simulates the credentials.ts module code that runs at runtime
30
+ */
31
+ const credentialsModuleCode = `
32
+ const { createDecipheriv } = require('node:crypto');
33
+
34
+ const AUTH_TAG_LENGTH = 16;
35
+
36
+ function decryptCredentials(encrypted, iv, masterKey) {
37
+ const key = Buffer.from(masterKey, 'hex');
38
+ const ivBuffer = Buffer.from(iv, 'hex');
39
+ const combined = Buffer.from(encrypted, 'base64');
40
+
41
+ const ciphertext = combined.subarray(0, -AUTH_TAG_LENGTH);
42
+ const authTag = combined.subarray(-AUTH_TAG_LENGTH);
43
+
44
+ const decipher = createDecipheriv('aes-256-gcm', key, ivBuffer);
45
+ decipher.setAuthTag(authTag);
46
+
47
+ const plaintext = Buffer.concat([
48
+ decipher.update(ciphertext),
49
+ decipher.final(),
50
+ ]);
51
+
52
+ return JSON.parse(plaintext.toString('utf-8'));
53
+ }
54
+
55
+ // This simulates runtime behavior
56
+ const Credentials = (() => {
57
+ if (typeof __GKM_ENCRYPTED_CREDENTIALS__ === 'undefined' ||
58
+ typeof __GKM_CREDENTIALS_IV__ === 'undefined') {
59
+ return {};
60
+ }
61
+
62
+ const masterKey = process.env.GKM_MASTER_KEY;
63
+ if (!masterKey) {
64
+ return {};
65
+ }
66
+
67
+ try {
68
+ return decryptCredentials(
69
+ __GKM_ENCRYPTED_CREDENTIALS__,
70
+ __GKM_CREDENTIALS_IV__,
71
+ masterKey
72
+ );
73
+ } catch (error) {
74
+ return {};
75
+ }
76
+ })();
77
+
78
+ module.exports = { Credentials };
79
+ `;
80
+
81
+ describe('Credentials runtime integration', () => {
82
+ it('should decrypt credentials when globals and master key are set', () => {
83
+ const secrets = {
84
+ DATABASE_URL: 'postgresql://user:pass@localhost/db',
85
+ API_KEY: 'sk_test_123456',
86
+ WEBHOOK_SECRET: 'whsec_abc123',
87
+ };
88
+
89
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
90
+
91
+ // Create a VM context with the build-time injected globals
92
+ const context = vm.createContext({
93
+ __GKM_ENCRYPTED_CREDENTIALS__: encrypted,
94
+ __GKM_CREDENTIALS_IV__: iv,
95
+ process: {
96
+ env: {
97
+ GKM_MASTER_KEY: masterKey,
98
+ },
99
+ },
100
+ require: require,
101
+ Buffer: Buffer,
102
+ module: { exports: {} },
103
+ });
104
+
105
+ // Run the credentials module code
106
+ vm.runInContext(credentialsModuleCode, context);
107
+
108
+ // Get the exported Credentials
109
+ const { Credentials } = context.module.exports;
110
+
111
+ expect(Credentials).toEqual(secrets);
112
+ });
113
+
114
+ it('should return empty object when globals are not defined', () => {
115
+ const context = vm.createContext({
116
+ process: { env: {} },
117
+ require: require,
118
+ Buffer: Buffer,
119
+ module: { exports: {} },
120
+ });
121
+
122
+ vm.runInContext(credentialsModuleCode, context);
123
+
124
+ const { Credentials } = context.module.exports;
125
+ expect(Credentials).toEqual({});
126
+ });
127
+
128
+ it('should return empty object when master key is missing', () => {
129
+ const secrets = { KEY: 'value' };
130
+ const { encrypted, iv } = encryptSecrets(secrets);
131
+
132
+ const context = vm.createContext({
133
+ __GKM_ENCRYPTED_CREDENTIALS__: encrypted,
134
+ __GKM_CREDENTIALS_IV__: iv,
135
+ process: { env: {} }, // No GKM_MASTER_KEY
136
+ require: require,
137
+ Buffer: Buffer,
138
+ module: { exports: {} },
139
+ });
140
+
141
+ vm.runInContext(credentialsModuleCode, context);
142
+
143
+ const { Credentials } = context.module.exports;
144
+ expect(Credentials).toEqual({});
145
+ });
146
+
147
+ it('should return empty object when master key is wrong', () => {
148
+ const secrets = { KEY: 'value' };
149
+ const { encrypted, iv } = encryptSecrets(secrets);
150
+
151
+ const context = vm.createContext({
152
+ __GKM_ENCRYPTED_CREDENTIALS__: encrypted,
153
+ __GKM_CREDENTIALS_IV__: iv,
154
+ process: {
155
+ env: {
156
+ GKM_MASTER_KEY: '0'.repeat(64), // Wrong key
157
+ },
158
+ },
159
+ require: require,
160
+ Buffer: Buffer,
161
+ module: { exports: {} },
162
+ });
163
+
164
+ vm.runInContext(credentialsModuleCode, context);
165
+
166
+ const { Credentials } = context.module.exports;
167
+ expect(Credentials).toEqual({});
168
+ });
169
+
170
+ it('should handle complex secrets with special characters', () => {
171
+ const secrets = {
172
+ PRIVATE_KEY:
173
+ '-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----',
174
+ JSON_CONFIG: '{"key": "value", "nested": {"a": 1}}',
175
+ UNICODE: '你好世界 🔐',
176
+ };
177
+
178
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
179
+
180
+ const context = vm.createContext({
181
+ __GKM_ENCRYPTED_CREDENTIALS__: encrypted,
182
+ __GKM_CREDENTIALS_IV__: iv,
183
+ process: {
184
+ env: { GKM_MASTER_KEY: masterKey },
185
+ },
186
+ require: require,
187
+ Buffer: Buffer,
188
+ module: { exports: {} },
189
+ });
190
+
191
+ vm.runInContext(credentialsModuleCode, context);
192
+
193
+ const { Credentials } = context.module.exports;
194
+ expect(Credentials).toEqual(secrets);
195
+ });
196
+
197
+ it('should work with EnvironmentParser pattern', () => {
198
+ const secrets = {
199
+ DATABASE_URL: 'postgresql://user:pass@localhost/db',
200
+ PORT: '3000',
201
+ };
202
+
203
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
204
+
205
+ // Simulate: {...process.env, ...Credentials}
206
+ const processEnv = {
207
+ NODE_ENV: 'production',
208
+ HOST: 'localhost',
209
+ };
210
+
211
+ const context = vm.createContext({
212
+ __GKM_ENCRYPTED_CREDENTIALS__: encrypted,
213
+ __GKM_CREDENTIALS_IV__: iv,
214
+ process: {
215
+ env: {
216
+ ...processEnv,
217
+ GKM_MASTER_KEY: masterKey,
218
+ },
219
+ },
220
+ require: require,
221
+ Buffer: Buffer,
222
+ module: { exports: {} },
223
+ });
224
+
225
+ vm.runInContext(credentialsModuleCode, context);
226
+
227
+ const { Credentials } = context.module.exports;
228
+
229
+ // Simulate what user code does
230
+ const combinedEnv = { ...processEnv, ...Credentials };
231
+
232
+ expect(combinedEnv).toEqual({
233
+ NODE_ENV: 'production',
234
+ HOST: 'localhost',
235
+ DATABASE_URL: 'postgresql://user:pass@localhost/db',
236
+ PORT: '3000',
237
+ });
238
+ });
239
+ });
@@ -0,0 +1,136 @@
1
+ import { createCipheriv, randomBytes } from 'node:crypto';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { decryptCredentials } from '../credentials';
4
+
5
+ /**
6
+ * Helper to encrypt secrets (mirrors CLI encryption logic)
7
+ */
8
+ function encryptSecrets(secrets: Record<string, string>) {
9
+ const masterKey = randomBytes(32);
10
+ const iv = randomBytes(12);
11
+ const plaintext = JSON.stringify(secrets);
12
+
13
+ const cipher = createCipheriv('aes-256-gcm', masterKey, iv);
14
+ const ciphertext = Buffer.concat([
15
+ cipher.update(plaintext, 'utf-8'),
16
+ cipher.final(),
17
+ ]);
18
+ const authTag = cipher.getAuthTag();
19
+ const combined = Buffer.concat([ciphertext, authTag]);
20
+
21
+ return {
22
+ encrypted: combined.toString('base64'),
23
+ iv: iv.toString('hex'),
24
+ masterKey: masterKey.toString('hex'),
25
+ };
26
+ }
27
+
28
+ describe('decryptCredentials', () => {
29
+ it('should decrypt credentials from encrypted payload', () => {
30
+ const secrets = {
31
+ DATABASE_URL: 'postgresql://user:pass@localhost/db',
32
+ API_KEY: 'sk_test_123456',
33
+ };
34
+
35
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
36
+ const decrypted = decryptCredentials(encrypted, iv, masterKey);
37
+
38
+ expect(decrypted).toEqual(secrets);
39
+ });
40
+
41
+ it('should handle empty secrets', () => {
42
+ const secrets = {};
43
+
44
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
45
+ const decrypted = decryptCredentials(encrypted, iv, masterKey);
46
+
47
+ expect(decrypted).toEqual({});
48
+ });
49
+
50
+ it('should handle secrets with special characters', () => {
51
+ const secrets = {
52
+ PASSWORD: 'p@ss/word!#$%^&*(){}[]|\\:";\'<>,.?/',
53
+ WEBHOOK_URL: 'https://api.example.com/webhook?token=abc&verify=true',
54
+ };
55
+
56
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
57
+ const decrypted = decryptCredentials(encrypted, iv, masterKey);
58
+
59
+ expect(decrypted).toEqual(secrets);
60
+ });
61
+
62
+ it('should handle secrets with unicode characters', () => {
63
+ const secrets = {
64
+ MESSAGE: '你好世界 🔐 مرحبا العالم',
65
+ EMOJI: '🚀💻🔑',
66
+ };
67
+
68
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
69
+ const decrypted = decryptCredentials(encrypted, iv, masterKey);
70
+
71
+ expect(decrypted).toEqual(secrets);
72
+ });
73
+
74
+ it('should throw with wrong master key', () => {
75
+ const secrets = { KEY: 'value' };
76
+ const { encrypted, iv } = encryptSecrets(secrets);
77
+
78
+ const wrongKey = '0'.repeat(64);
79
+
80
+ expect(() => decryptCredentials(encrypted, iv, wrongKey)).toThrow();
81
+ });
82
+
83
+ it('should throw with wrong IV', () => {
84
+ const secrets = { KEY: 'value' };
85
+ const { encrypted, masterKey } = encryptSecrets(secrets);
86
+
87
+ const wrongIv = '0'.repeat(24);
88
+
89
+ expect(() => decryptCredentials(encrypted, wrongIv, masterKey)).toThrow();
90
+ });
91
+
92
+ it('should throw with tampered ciphertext', () => {
93
+ const secrets = { KEY: 'value' };
94
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
95
+
96
+ const tamperedBuffer = Buffer.from(encrypted, 'base64');
97
+ tamperedBuffer[0] = tamperedBuffer[0] ^ 0xff;
98
+ const tampered = tamperedBuffer.toString('base64');
99
+
100
+ expect(() => decryptCredentials(tampered, iv, masterKey)).toThrow();
101
+ });
102
+
103
+ it('should handle large number of secrets', () => {
104
+ const secrets: Record<string, string> = {};
105
+ for (let i = 0; i < 50; i++) {
106
+ secrets[`KEY_${i}`] = `value-${i}-${'x'.repeat(50)}`;
107
+ }
108
+
109
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
110
+ const decrypted = decryptCredentials(encrypted, iv, masterKey);
111
+
112
+ expect(decrypted).toEqual(secrets);
113
+ });
114
+
115
+ it('should handle secrets with newlines', () => {
116
+ const secrets = {
117
+ PRIVATE_KEY:
118
+ '-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----',
119
+ MULTILINE: 'line1\nline2\nline3',
120
+ };
121
+
122
+ const { encrypted, iv, masterKey } = encryptSecrets(secrets);
123
+ const decrypted = decryptCredentials(encrypted, iv, masterKey);
124
+
125
+ expect(decrypted).toEqual(secrets);
126
+ });
127
+ });
128
+
129
+ describe('Credentials export', () => {
130
+ it('should return empty object in development mode (no globals defined)', async () => {
131
+ // In test environment, __GKM_ENCRYPTED_CREDENTIALS__ is undefined
132
+ // so Credentials should be an empty object
133
+ const { Credentials } = await import('../credentials');
134
+ expect(Credentials).toEqual({});
135
+ });
136
+ });