@geekmidas/envkit 9.0.2 → 10.0.0-alpha.1

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 (50) hide show
  1. package/README.md +2 -2
  2. package/dist/{EnvironmentBuilder-DDgLJAAo.cjs → EnvironmentBuilder-15SdFJdK.cjs} +2 -2
  3. package/dist/EnvironmentBuilder-15SdFJdK.cjs.map +1 -0
  4. package/dist/{EnvironmentBuilder-CFen3oIg.mjs → EnvironmentBuilder-C-2fViCT.mjs} +2 -2
  5. package/dist/EnvironmentBuilder-C-2fViCT.mjs.map +1 -0
  6. package/dist/{EnvironmentBuilder-DHfDXJUm.d.mts → EnvironmentBuilder-CoBQ9Xp2.d.mts} +2 -2
  7. package/dist/{EnvironmentBuilder-CNgcdzSR.d.cts.map → EnvironmentBuilder-CoBQ9Xp2.d.mts.map} +1 -1
  8. package/dist/{EnvironmentBuilder-CNgcdzSR.d.cts → EnvironmentBuilder-DeIle4QN.d.cts} +2 -2
  9. package/dist/{EnvironmentBuilder-DHfDXJUm.d.mts.map → EnvironmentBuilder-DeIle4QN.d.cts.map} +1 -1
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.mts +1 -1
  13. package/dist/index.mjs +1 -1
  14. package/dist/sst.cjs +13 -6
  15. package/dist/sst.cjs.map +1 -1
  16. package/dist/sst.d.cts +15 -4
  17. package/dist/sst.d.cts.map +1 -1
  18. package/dist/sst.d.mts +15 -4
  19. package/dist/sst.d.mts.map +1 -1
  20. package/dist/sst.mjs +13 -6
  21. package/dist/sst.mjs.map +1 -1
  22. package/package.json +6 -2
  23. package/CHANGELOG.md +0 -115
  24. package/dist/EnvironmentBuilder-CFen3oIg.mjs.map +0 -1
  25. package/dist/EnvironmentBuilder-DDgLJAAo.cjs.map +0 -1
  26. package/docs/api-reference.md +0 -302
  27. package/docs/async-secrets-design.md +0 -355
  28. package/examples/basic-usage.ts +0 -386
  29. package/src/EnvironmentBuilder.ts +0 -192
  30. package/src/EnvironmentParser.ts +0 -330
  31. package/src/SnifferEnvironmentParser.ts +0 -334
  32. package/src/SstEnvValidator.ts +0 -369
  33. package/src/SstEnvironmentBuilder.ts +0 -343
  34. package/src/__tests__/ConfigParser.spec.ts +0 -394
  35. package/src/__tests__/EnvironmentBuilder.spec.ts +0 -254
  36. package/src/__tests__/EnvironmentParser.spec.ts +0 -839
  37. package/src/__tests__/SnifferEnvironmentParser.spec.ts +0 -644
  38. package/src/__tests__/SstEnvValidator.spec.ts +0 -236
  39. package/src/__tests__/SstEnvironmentBuilder.spec.ts +0 -397
  40. package/src/__tests__/credentials.integration.spec.ts +0 -239
  41. package/src/__tests__/credentials.spec.ts +0 -136
  42. package/src/__tests__/formatter.spec.ts +0 -268
  43. package/src/__tests__/sst.spec.ts +0 -437
  44. package/src/credentials.ts +0 -112
  45. package/src/formatter.ts +0 -146
  46. package/src/index.ts +0 -24
  47. package/src/sst.ts +0 -76
  48. package/sst-env.d.ts +0 -8
  49. package/tsconfig.json +0 -9
  50. package/tsdown.config.ts +0 -18
@@ -1,437 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import {
3
- type ApiGatewayV2,
4
- type Bucket,
5
- environmentCase,
6
- type Function,
7
- normalizeResourceEnv,
8
- type Postgres,
9
- ResourceType,
10
- type Secret,
11
- type Vpc,
12
- } from '../sst';
13
-
14
- describe('sst', () => {
15
- describe('environmentCase', () => {
16
- it('should convert camelCase to UPPER_SNAKE_CASE', () => {
17
- expect(environmentCase('myVariable')).toBe('MY_VARIABLE');
18
- expect(environmentCase('somePropertyName')).toBe('SOME_PROPERTY_NAME');
19
- expect(environmentCase('APIKey')).toBe('API_KEY');
20
- });
21
-
22
- it('should convert snake_case to UPPER_SNAKE_CASE', () => {
23
- expect(environmentCase('my_variable')).toBe('MY_VARIABLE');
24
- expect(environmentCase('some_property_name')).toBe('SOME_PROPERTY_NAME');
25
- });
26
-
27
- it('should handle numbers correctly', () => {
28
- // The function only removes underscore before digits
29
- expect(environmentCase('api_v2')).toBe('API_V2');
30
- expect(environmentCase('value_123')).toBe('VALUE123');
31
- expect(environmentCase('test_1_thing')).toBe('TEST1_THING');
32
- expect(environmentCase('api_2')).toBe('API2');
33
- });
34
-
35
- it('should handle already uppercase strings', () => {
36
- expect(environmentCase('ALREADY_UPPER')).toBe('ALREADY_UPPER');
37
- expect(environmentCase('TEST')).toBe('TEST');
38
- });
39
-
40
- it('should handle kebab-case', () => {
41
- expect(environmentCase('my-variable')).toBe('MY_VARIABLE');
42
- expect(environmentCase('kebab-case-example')).toBe('KEBAB_CASE_EXAMPLE');
43
- });
44
-
45
- it('should handle mixed cases', () => {
46
- expect(environmentCase('MixedCASE')).toBe('MIXED_CASE');
47
- expect(environmentCase('XMLParser')).toBe('XML_PARSER');
48
- expect(environmentCase('IOStream')).toBe('IO_STREAM');
49
- });
50
-
51
- it('should handle empty string', () => {
52
- expect(environmentCase('')).toBe('');
53
- });
54
-
55
- it('should handle single character', () => {
56
- expect(environmentCase('a')).toBe('A');
57
- expect(environmentCase('A')).toBe('A');
58
- });
59
-
60
- it('should handle strings with special characters', () => {
61
- expect(environmentCase('my.variable')).toBe('MY_VARIABLE');
62
- expect(environmentCase('my@variable')).toBe('MY_VARIABLE');
63
- expect(environmentCase('my#variable')).toBe('MY_VARIABLE');
64
- });
65
- });
66
-
67
- describe('normalizeResourceEnv', () => {
68
- describe('string values', () => {
69
- it('should convert string values to environment case', () => {
70
- const input = {
71
- apiUrl: 'https://api.example.com',
72
- secretKey: 'my-secret-key',
73
- NODE_ENV: 'production',
74
- };
75
-
76
- const result = normalizeResourceEnv(input);
77
-
78
- expect(result).toEqual({
79
- API_URL: 'https://api.example.com',
80
- SECRET_KEY: 'my-secret-key',
81
- NODE_ENV: 'production',
82
- });
83
- });
84
-
85
- it('should handle empty object', () => {
86
- expect(normalizeResourceEnv({})).toEqual({});
87
- });
88
- });
89
-
90
- describe('Secret resource', () => {
91
- it('should process Secret resource correctly', () => {
92
- const secret: Secret = {
93
- type: ResourceType.Secret,
94
- value: 'super-secret-value',
95
- };
96
-
97
- const result = normalizeResourceEnv({
98
- mySecret: secret,
99
- });
100
-
101
- expect(result).toEqual({
102
- MY_SECRET: 'super-secret-value',
103
- });
104
- });
105
-
106
- it('should process SSTSecret resource correctly', () => {
107
- const secret = {
108
- type: ResourceType.SSTSecret as ResourceType.SSTSecret,
109
- value: 'another-secret',
110
- };
111
-
112
- const result = normalizeResourceEnv({
113
- appSecret: secret as any,
114
- });
115
-
116
- expect(result).toEqual({
117
- APP_SECRET: 'another-secret',
118
- });
119
- });
120
- });
121
-
122
- describe('Postgres resource', () => {
123
- it('should process Postgres resource correctly', () => {
124
- const postgres: Postgres = {
125
- type: ResourceType.Postgres,
126
- database: 'myapp',
127
- host: 'localhost',
128
- password: 'password123',
129
- port: 5432,
130
- username: 'postgres',
131
- };
132
-
133
- const result = normalizeResourceEnv({
134
- database: postgres,
135
- });
136
-
137
- expect(result).toEqual({
138
- DATABASE_NAME: 'myapp',
139
- DATABASE_HOST: 'localhost',
140
- DATABASE_PASSWORD: 'password123',
141
- DATABASE_PORT: 5432,
142
- DATABASE_USERNAME: 'postgres',
143
- DATABASE_URL:
144
- 'postgresql://postgres:password123@localhost:5432/myapp',
145
- });
146
- });
147
-
148
- it('should process SSTPostgres resource correctly', () => {
149
- const postgres = {
150
- type: ResourceType.SSTPostgres as ResourceType.SSTPostgres,
151
- database: 'prod_db',
152
- host: 'prod.example.com',
153
- password: 'prod-password',
154
- port: 5433,
155
- username: 'prod_user',
156
- };
157
-
158
- const result = normalizeResourceEnv({
159
- mainDb: postgres as any,
160
- });
161
-
162
- expect(result).toEqual({
163
- MAIN_DB_NAME: 'prod_db',
164
- MAIN_DB_HOST: 'prod.example.com',
165
- MAIN_DB_PASSWORD: 'prod-password',
166
- MAIN_DB_PORT: 5433,
167
- MAIN_DB_URL:
168
- 'postgresql://prod_user:prod-password@prod.example.com:5433/prod_db',
169
- MAIN_DB_USERNAME: 'prod_user',
170
- });
171
- });
172
- });
173
-
174
- describe('Queue resource', () => {
175
- it('emits url, arn, and an SQS publisher connection string', () => {
176
- const result = normalizeResourceEnv({
177
- orders: {
178
- type: ResourceType.Queue,
179
- url: 'https://sqs.us-east-1.amazonaws.com/123/shop-orders',
180
- arn: 'arn:aws:sqs:us-east-1:123:shop-orders',
181
- },
182
- });
183
-
184
- expect(result).toEqual({
185
- ORDERS_URL: 'https://sqs.us-east-1.amazonaws.com/123/shop-orders',
186
- ORDERS_ARN: 'arn:aws:sqs:us-east-1:123:shop-orders',
187
- ORDERS_PUBLISHER_CONNECTION_STRING:
188
- 'sqs://?queueUrl=https%3A%2F%2Fsqs.us-east-1.amazonaws.com%2F123%2Fshop-orders',
189
- });
190
- });
191
- });
192
-
193
- describe('SnsTopic resource', () => {
194
- it('emits arn and an SNS publisher connection string', () => {
195
- const result = normalizeResourceEnv({
196
- events: {
197
- type: ResourceType.SnsTopic,
198
- arn: 'arn:aws:sns:us-east-1:123:shop-events',
199
- },
200
- });
201
-
202
- expect(result).toEqual({
203
- EVENTS_ARN: 'arn:aws:sns:us-east-1:123:shop-events',
204
- EVENTS_PUBLISHER_CONNECTION_STRING:
205
- 'sns://?topicArn=arn%3Aaws%3Asns%3Aus-east-1%3A123%3Ashop-events',
206
- });
207
- });
208
- });
209
-
210
- describe('Bucket resource', () => {
211
- it('should process Bucket resource correctly', () => {
212
- const bucket: Bucket = {
213
- type: ResourceType.Bucket,
214
- name: 'my-s3-bucket',
215
- };
216
-
217
- const result = normalizeResourceEnv({
218
- uploadBucket: bucket,
219
- });
220
-
221
- expect(result).toEqual({
222
- UPLOAD_BUCKET_NAME: 'my-s3-bucket',
223
- });
224
- });
225
-
226
- it('should process SSTBucket resource correctly', () => {
227
- const bucket = {
228
- type: ResourceType.SSTBucket as ResourceType.SSTBucket,
229
- name: 'assets-bucket-prod',
230
- };
231
-
232
- const result = normalizeResourceEnv({
233
- assetStorage: bucket as any,
234
- });
235
-
236
- expect(result).toEqual({
237
- ASSET_STORAGE_NAME: 'assets-bucket-prod',
238
- });
239
- });
240
- });
241
-
242
- describe('noop resources', () => {
243
- it('should not add environment variables for ApiGatewayV2', () => {
244
- const api: ApiGatewayV2 = {
245
- type: ResourceType.ApiGatewayV2,
246
- url: 'https://api.example.com',
247
- };
248
-
249
- const result = normalizeResourceEnv({
250
- api: api,
251
- });
252
-
253
- expect(result).toEqual({});
254
- });
255
-
256
- it('should not add environment variables for Function', () => {
257
- const fn: Function = {
258
- type: ResourceType.Function,
259
- name: 'my-lambda',
260
- };
261
-
262
- const result = normalizeResourceEnv({
263
- handler: fn,
264
- });
265
-
266
- expect(result).toEqual({});
267
- });
268
-
269
- it('should not add environment variables for Vpc', () => {
270
- const vpc: Vpc = {
271
- type: ResourceType.Vpc,
272
- bastion: 'bastion-host',
273
- };
274
-
275
- const result = normalizeResourceEnv({
276
- network: vpc,
277
- });
278
-
279
- expect(result).toEqual({});
280
- });
281
-
282
- it('should handle all SST noop resource types', () => {
283
- const api = {
284
- type: ResourceType.SSTApiGatewayV2 as ResourceType.SSTApiGatewayV2,
285
- url: 'https://api.example.com',
286
- };
287
-
288
- const fn = {
289
- type: ResourceType.SSTFunction as ResourceType.SSTFunction,
290
- name: 'my-function',
291
- };
292
-
293
- const result = normalizeResourceEnv({
294
- api: api as any,
295
- function: fn as any,
296
- });
297
-
298
- expect(result).toEqual({});
299
- });
300
- });
301
-
302
- describe('mixed resources', () => {
303
- it('should handle mix of strings and resources', () => {
304
- const postgres: Postgres = {
305
- type: ResourceType.Postgres,
306
- database: 'app_db',
307
- host: 'db.example.com',
308
- password: 'db-pass',
309
- port: 5432,
310
- username: 'app_user',
311
- };
312
-
313
- const secret: Secret = {
314
- type: ResourceType.Secret,
315
- value: 'jwt-secret',
316
- };
317
-
318
- const bucket: Bucket = {
319
- type: ResourceType.Bucket,
320
- name: 'uploads-bucket',
321
- };
322
-
323
- const result = normalizeResourceEnv({
324
- nodeEnv: 'production',
325
- appName: 'My App',
326
- database: postgres,
327
- jwtSecret: secret,
328
- uploads: bucket,
329
- apiVersion: 'v2',
330
- });
331
-
332
- expect(result).toEqual({
333
- NODE_ENV: 'production',
334
- APP_NAME: 'My App',
335
- DATABASE_NAME: 'app_db',
336
- DATABASE_HOST: 'db.example.com',
337
- DATABASE_PASSWORD: 'db-pass',
338
- DATABASE_PORT: 5432,
339
- DATABASE_URL:
340
- 'postgresql://app_user:db-pass@db.example.com:5432/app_db',
341
- DATABASE_USERNAME: 'app_user',
342
- JWT_SECRET: 'jwt-secret',
343
- UPLOADS_NAME: 'uploads-bucket',
344
- API_VERSION: 'v2',
345
- });
346
- });
347
- });
348
-
349
- describe('edge cases', () => {
350
- it('should handle resources with special characters in keys', () => {
351
- const secret: Secret = {
352
- type: ResourceType.Secret,
353
- value: 'value',
354
- };
355
-
356
- const result = normalizeResourceEnv({
357
- 'my-secret-key': secret,
358
- 'another.secret': secret,
359
- 'secret@123': secret,
360
- });
361
-
362
- expect(result).toEqual({
363
- MY_SECRET_KEY: 'value',
364
- ANOTHER_SECRET: 'value',
365
- SECRET123: 'value',
366
- });
367
- });
368
-
369
- it('should handle numeric values in postgres port', () => {
370
- const postgres: Postgres = {
371
- type: ResourceType.Postgres,
372
- database: 'test',
373
- host: 'localhost',
374
- password: 'pass',
375
- port: 5432,
376
- username: 'user',
377
- };
378
-
379
- const result = normalizeResourceEnv({
380
- db: postgres,
381
- });
382
-
383
- expect(result.DB_PORT).toBe(5432);
384
- expect(typeof result.DB_PORT).toBe('number');
385
- });
386
-
387
- it('should handle very long keys', () => {
388
- const secret: Secret = {
389
- type: ResourceType.Secret,
390
- value: 'value',
391
- };
392
-
393
- const result = normalizeResourceEnv({
394
- thisIsAVeryLongKeyNameThatShouldBeConvertedProperly: secret,
395
- });
396
-
397
- expect(result).toEqual({
398
- THIS_IS_A_VERY_LONG_KEY_NAME_THAT_SHOULD_BE_CONVERTED_PROPERLY:
399
- 'value',
400
- });
401
- });
402
-
403
- it('should handle multiple underscores and numbers', () => {
404
- const bucket: Bucket = {
405
- type: ResourceType.Bucket,
406
- name: 'test-bucket',
407
- };
408
-
409
- const result = normalizeResourceEnv({
410
- s3_bucket_v2_1: bucket,
411
- bucket_123_456: bucket,
412
- });
413
-
414
- expect(result).toEqual({
415
- S3_BUCKET_V21_NAME: 'test-bucket',
416
- BUCKET123456_NAME: 'test-bucket',
417
- });
418
- });
419
- });
420
- });
421
-
422
- describe('ResourceType enum', () => {
423
- it('should have all expected resource types', () => {
424
- expect(ResourceType.ApiGatewayV2).toBe('sst.aws.ApiGatewayV2');
425
- expect(ResourceType.Postgres).toBe('sst.aws.Postgres');
426
- expect(ResourceType.Function).toBe('sst.aws.Function');
427
- expect(ResourceType.Bucket).toBe('sst.aws.Bucket');
428
- expect(ResourceType.Vpc).toBe('sst.aws.Vpc');
429
- expect(ResourceType.Secret).toBe('sst.sst.Secret');
430
- expect(ResourceType.SSTSecret).toBe('sst:sst:Secret');
431
- expect(ResourceType.SSTFunction).toBe('sst:sst:Function');
432
- expect(ResourceType.SSTApiGatewayV2).toBe('sst:aws:ApiGatewayV2');
433
- expect(ResourceType.SSTPostgres).toBe('sst:aws:Postgres');
434
- expect(ResourceType.SSTBucket).toBe('sst:aws:Bucket');
435
- });
436
- });
437
- });
@@ -1,112 +0,0 @@
1
- import { createDecipheriv } from 'node:crypto';
2
-
3
- /**
4
- * Build-time injected encrypted credentials.
5
- * These are replaced by tsdown/esbuild --define at build time.
6
- */
7
- declare const __GKM_ENCRYPTED_CREDENTIALS__: string | undefined;
8
- declare const __GKM_CREDENTIALS_IV__: string | undefined;
9
-
10
- /** AES-256-GCM auth tag length */
11
- const AUTH_TAG_LENGTH = 16;
12
-
13
- /**
14
- * Decrypt credentials from encrypted payload.
15
- * Exported for testing purposes.
16
- */
17
- export function decryptCredentials(
18
- encrypted: string,
19
- iv: string,
20
- masterKey: string,
21
- ): Record<string, string> {
22
- const key = Buffer.from(masterKey, 'hex');
23
- const ivBuffer = Buffer.from(iv, 'hex');
24
- const combined = Buffer.from(encrypted, 'base64');
25
-
26
- // Split ciphertext and auth tag
27
- const ciphertext = combined.subarray(0, -AUTH_TAG_LENGTH);
28
- const authTag = combined.subarray(-AUTH_TAG_LENGTH);
29
-
30
- // Decrypt using AES-256-GCM
31
- const decipher = createDecipheriv('aes-256-gcm', key, ivBuffer);
32
- decipher.setAuthTag(authTag);
33
-
34
- const plaintext = Buffer.concat([
35
- decipher.update(ciphertext),
36
- decipher.final(),
37
- ]);
38
-
39
- return JSON.parse(plaintext.toString('utf-8'));
40
- }
41
-
42
- /**
43
- * Credentials object for use with EnvironmentParser.
44
- *
45
- * Resolution order:
46
- * 1. **Dev mode (gkm dev/exec)**: Checks `globalThis.__gkm_credentials__` for
47
- * credentials injected by the CLI preload script. This approach survives
48
- * CJS/ESM module duplication (where mutating the export would only affect one copy).
49
- * 2. **Production mode**: Decrypts build-time embedded credentials using the
50
- * `GKM_MASTER_KEY` environment variable (AES-256-GCM).
51
- * 3. **Fallback**: Returns empty object (allows graceful fallback to process.env).
52
- *
53
- * @example
54
- * ```typescript
55
- * import { EnvironmentParser } from '@geekmidas/envkit';
56
- * import { Credentials } from '@geekmidas/envkit/credentials';
57
- *
58
- * export const envParser = new EnvironmentParser({...process.env, ...Credentials})
59
- * .create((get) => ({
60
- * database: {
61
- * url: get('DATABASE_URL').string(),
62
- * },
63
- * }))
64
- * .parse();
65
- * ```
66
- */
67
- export const Credentials: Record<string, string> = (() => {
68
- // Dev mode: check if gkm exec/dev preload injected credentials via globalThis
69
- // This survives CJS/ESM module duplication where Object.assign on the
70
- // export would only mutate one copy of the module's Credentials object.
71
- const injected = (globalThis as Record<string, unknown>)
72
- .__gkm_credentials__ as Record<string, string> | undefined;
73
- if (injected) {
74
- return injected;
75
- }
76
-
77
- // Development mode - no credentials embedded at build time
78
- if (
79
- typeof __GKM_ENCRYPTED_CREDENTIALS__ === 'undefined' ||
80
- typeof __GKM_CREDENTIALS_IV__ === 'undefined'
81
- ) {
82
- return {};
83
- }
84
-
85
- // Production mode - decrypt credentials using master key
86
- const masterKey = process.env.GKM_MASTER_KEY;
87
-
88
- if (!masterKey) {
89
- // Log warning but don't throw - allows graceful fallback to env vars
90
- console.error(
91
- '[gkm] GKM_MASTER_KEY environment variable is required to decrypt credentials.',
92
- );
93
- console.error(
94
- '[gkm] Falling back to environment variables. Some secrets may be missing.',
95
- );
96
- return {};
97
- }
98
-
99
- try {
100
- return decryptCredentials(
101
- __GKM_ENCRYPTED_CREDENTIALS__,
102
- __GKM_CREDENTIALS_IV__,
103
- masterKey,
104
- );
105
- } catch (error) {
106
- console.error('[gkm] Failed to decrypt credentials:', error);
107
- console.error('[gkm] Falling back to environment variables.');
108
- return {};
109
- }
110
- })();
111
-
112
- export default Credentials;
package/src/formatter.ts DELETED
@@ -1,146 +0,0 @@
1
- import type { z } from 'zod/v4';
2
-
3
- /**
4
- * Options for formatting parse errors.
5
- */
6
- export interface FormatOptions {
7
- /** Whether to use colors in output. Defaults to auto-detect TTY. */
8
- colors?: boolean;
9
- }
10
-
11
- /**
12
- * ANSI color codes for terminal output.
13
- */
14
- const colors = {
15
- reset: '\x1b[0m',
16
- red: '\x1b[31m',
17
- yellow: '\x1b[33m',
18
- cyan: '\x1b[36m',
19
- dim: '\x1b[2m',
20
- bold: '\x1b[1m',
21
- };
22
-
23
- /**
24
- * Formats a ZodError into a user-friendly string for development.
25
- *
26
- * @param error - The ZodError to format
27
- * @param options - Formatting options
28
- * @returns Formatted error message
29
- *
30
- * @example
31
- * ```typescript
32
- * try {
33
- * config.parse();
34
- * } catch (error) {
35
- * if (error instanceof ZodError) {
36
- * console.error(formatParseError(error));
37
- * }
38
- * }
39
- * ```
40
- *
41
- * Output:
42
- * ```
43
- * Environment Configuration Failed
44
- *
45
- * Missing Variables:
46
- * DATABASE_URL - Required
47
- * JWT_SECRET - Required
48
- *
49
- * Invalid Values:
50
- * NODE_ENV = "invalid"
51
- * Expected: "development" | "staging" | "production"
52
- * ```
53
- */
54
- export function formatParseError(
55
- error: z.ZodError,
56
- options: FormatOptions = {},
57
- ): string {
58
- const useColors =
59
- options.colors ?? (process.stdout?.isTTY && process.env.NO_COLOR == null);
60
-
61
- const c = useColors
62
- ? colors
63
- : { reset: '', red: '', yellow: '', cyan: '', dim: '', bold: '' };
64
-
65
- const missingVars: Array<{ name: string; message: string }> = [];
66
- const invalidVars: Array<{ name: string; value: unknown; message: string }> =
67
- [];
68
-
69
- for (const issue of error.issues) {
70
- // Extract environment variable name from path or message
71
- let envName = '';
72
- if (issue.path.length > 0) {
73
- // Join the full path with '.' to show nested config keys and env var name
74
- envName = issue.path.map(String).join('.');
75
- } else {
76
- // Try to extract from message like 'Environment variable "NAME": ...'
77
- const match = issue.message.match(/Environment variable "([^"]+)"/);
78
- if (match?.[1]) {
79
- envName = match[1];
80
- }
81
- }
82
-
83
- // Determine if this is a missing or invalid value
84
- // Use type guard for received property
85
- const received = 'received' in issue ? issue.received : undefined;
86
- const isMissing =
87
- issue.code === 'invalid_type' &&
88
- (received === 'undefined' || received === 'null');
89
-
90
- if (isMissing) {
91
- missingVars.push({
92
- name: envName || 'Unknown',
93
- message: cleanMessage(issue.message),
94
- });
95
- } else {
96
- invalidVars.push({
97
- name: envName || 'Unknown',
98
- value: received,
99
- message: cleanMessage(issue.message),
100
- });
101
- }
102
- }
103
-
104
- const lines: string[] = [];
105
-
106
- lines.push('');
107
- lines.push(`${c.red}${c.bold}Environment Configuration Failed${c.reset}`);
108
- lines.push('');
109
-
110
- if (missingVars.length > 0) {
111
- lines.push(`${c.yellow}Missing Variables:${c.reset}`);
112
- for (const v of missingVars) {
113
- lines.push(` ${c.cyan}${v.name}${c.reset} ${c.dim}- Required${c.reset}`);
114
- }
115
- lines.push('');
116
- }
117
-
118
- if (invalidVars.length > 0) {
119
- lines.push(`${c.yellow}Invalid Values:${c.reset}`);
120
- for (const v of invalidVars) {
121
- const valueStr =
122
- v.value !== undefined ? ` = ${JSON.stringify(v.value)}` : '';
123
- lines.push(` ${c.cyan}${v.name}${c.reset}${valueStr}`);
124
- lines.push(` ${c.dim}${v.message}${c.reset}`);
125
- }
126
- lines.push('');
127
- }
128
-
129
- return lines.join('\n');
130
- }
131
-
132
- /**
133
- * Cleans up a Zod error message by removing redundant prefixes.
134
- */
135
- function cleanMessage(message: string): string {
136
- // Remove "Environment variable "NAME": " prefix if present
137
- return message.replace(/^Environment variable "[^"]+": /, '');
138
- }
139
-
140
- /**
141
- * Checks if the current environment is development.
142
- */
143
- export function isDevelopment(): boolean {
144
- const nodeEnv = process.env.NODE_ENV?.toLowerCase();
145
- return nodeEnv == null || nodeEnv === 'development' || nodeEnv === 'dev';
146
- }
package/src/index.ts DELETED
@@ -1,24 +0,0 @@
1
- export {
2
- EnvironmentBuilder,
3
- type EnvironmentBuilderOptions,
4
- type EnvironmentResolver,
5
- type EnvRecord,
6
- type EnvValue,
7
- environmentCase,
8
- type InputValue,
9
- type Resolvers,
10
- type TypedInputValue,
11
- type TypedResolvers,
12
- } from './EnvironmentBuilder';
13
- export {
14
- ConfigParser,
15
- type EmptyObject,
16
- type EnvFetcher,
17
- EnvironmentParser,
18
- type InferConfig,
19
- } from './EnvironmentParser';
20
- export {
21
- type FormatOptions,
22
- formatParseError,
23
- isDevelopment,
24
- } from './formatter';