core-services-sdk 1.3.75 → 1.3.76
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.
package/package.json
CHANGED
|
@@ -307,3 +307,57 @@ export function formatEnvReport(report) {
|
|
|
307
307
|
report.success ? 'All variables are valid.' : 'Some variables are invalid.',
|
|
308
308
|
].join('\n')
|
|
309
309
|
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Validates environment variables end-to-end and builds a full report.
|
|
313
|
+
*
|
|
314
|
+
* This function:
|
|
315
|
+
* - validates values using the schema definition
|
|
316
|
+
* - builds a structured report (including masking secrets)
|
|
317
|
+
* - formats a printable output
|
|
318
|
+
*
|
|
319
|
+
* @param {Record<string, Object>} definition
|
|
320
|
+
* Environment schema definition.
|
|
321
|
+
*
|
|
322
|
+
* @param {Record<string, any>} values
|
|
323
|
+
* Raw environment values (e.g. process.env).
|
|
324
|
+
*
|
|
325
|
+
* @param {{
|
|
326
|
+
* mask?: (value: any) => string
|
|
327
|
+
* }} [options]
|
|
328
|
+
*
|
|
329
|
+
* @returns {{
|
|
330
|
+
* success: boolean,
|
|
331
|
+
* validation: {
|
|
332
|
+
* success: boolean,
|
|
333
|
+
* data?: Record<string, any>,
|
|
334
|
+
* summary?: Record<string, string[]>
|
|
335
|
+
* },
|
|
336
|
+
* report: {
|
|
337
|
+
* success: boolean,
|
|
338
|
+
* params: Array<{
|
|
339
|
+
* key: string,
|
|
340
|
+
* value: any,
|
|
341
|
+
* displayValue: string,
|
|
342
|
+
* secret: boolean,
|
|
343
|
+
* valid: boolean,
|
|
344
|
+
* errors?: string[]
|
|
345
|
+
* }>
|
|
346
|
+
* },
|
|
347
|
+
* output: string
|
|
348
|
+
* }}
|
|
349
|
+
*/
|
|
350
|
+
export function validateAndReportEnv(definition, values, options = {}) {
|
|
351
|
+
const { mask } = options
|
|
352
|
+
|
|
353
|
+
const validation = validateEnv(definition, values)
|
|
354
|
+
const report = buildEnvReport(definition, values, validation, mask)
|
|
355
|
+
const output = formatEnvReport(report)
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
success: validation.success,
|
|
359
|
+
validation,
|
|
360
|
+
report,
|
|
361
|
+
output,
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { validateAndReportEnv } from '../../src/env/env-validation.js'
|
|
3
|
+
|
|
4
|
+
// mask example
|
|
5
|
+
const mask = (value) => {
|
|
6
|
+
if (value == null) {
|
|
7
|
+
return '******'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const str = String(value)
|
|
11
|
+
if (str.length <= 4) {
|
|
12
|
+
return '****'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return `${str.slice(0, 2)}****${str.slice(-2)}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Definitions
|
|
20
|
+
*/
|
|
21
|
+
const definition = {
|
|
22
|
+
PORT: {
|
|
23
|
+
type: 'number',
|
|
24
|
+
required: true,
|
|
25
|
+
int: true,
|
|
26
|
+
min: 1,
|
|
27
|
+
max: 65535,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
TIMEOUT: {
|
|
31
|
+
type: 'number',
|
|
32
|
+
min: 100,
|
|
33
|
+
max: 10000,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
RETRIES: {
|
|
37
|
+
type: 'number',
|
|
38
|
+
int: true,
|
|
39
|
+
min: 0,
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
DEBUG: {
|
|
43
|
+
type: 'boolean',
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
MODE: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
enum: ['development', 'production'],
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
API_KEY: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
secret: true,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Values scenarios
|
|
59
|
+
*/
|
|
60
|
+
const scenarios = [
|
|
61
|
+
{
|
|
62
|
+
title: '✅ All valid values',
|
|
63
|
+
values: {
|
|
64
|
+
PORT: 3000,
|
|
65
|
+
TIMEOUT: 500,
|
|
66
|
+
RETRIES: 3,
|
|
67
|
+
DEBUG: false,
|
|
68
|
+
MODE: 'development',
|
|
69
|
+
API_KEY: 'secret123',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: '❌ Number too small (PORT)',
|
|
74
|
+
values: {
|
|
75
|
+
PORT: 0,
|
|
76
|
+
TIMEOUT: 500,
|
|
77
|
+
RETRIES: 3,
|
|
78
|
+
DEBUG: true,
|
|
79
|
+
MODE: 'production',
|
|
80
|
+
API_KEY: 'secret123',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
title: '❌ Number too large (TIMEOUT)',
|
|
85
|
+
values: {
|
|
86
|
+
PORT: 3000,
|
|
87
|
+
TIMEOUT: 20000,
|
|
88
|
+
RETRIES: 1,
|
|
89
|
+
DEBUG: false,
|
|
90
|
+
MODE: 'development',
|
|
91
|
+
API_KEY: 'secret123',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
title: '❌ Invalid number (string)',
|
|
96
|
+
values: {
|
|
97
|
+
PORT: 'abc',
|
|
98
|
+
TIMEOUT: 500,
|
|
99
|
+
RETRIES: 2,
|
|
100
|
+
DEBUG: false,
|
|
101
|
+
MODE: 'development',
|
|
102
|
+
API_KEY: 'secret123',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
title: '❌ Invalid enum (MODE)',
|
|
107
|
+
values: {
|
|
108
|
+
PORT: 3000,
|
|
109
|
+
TIMEOUT: 500,
|
|
110
|
+
RETRIES: 2,
|
|
111
|
+
DEBUG: false,
|
|
112
|
+
MODE: 'prod',
|
|
113
|
+
API_KEY: 'secret123',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: '✅ Boolean coercion + missing secret',
|
|
118
|
+
values: {
|
|
119
|
+
PORT: '8080',
|
|
120
|
+
TIMEOUT: '1000',
|
|
121
|
+
RETRIES: '0',
|
|
122
|
+
DEBUG: 'true',
|
|
123
|
+
MODE: 'production',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Run scenarios
|
|
130
|
+
*/
|
|
131
|
+
for (const scenario of scenarios) {
|
|
132
|
+
console.log('\n===================================================')
|
|
133
|
+
console.log(scenario.title)
|
|
134
|
+
console.log('===================================================\n')
|
|
135
|
+
|
|
136
|
+
const result = validateAndReportEnv(definition, scenario.values, { mask })
|
|
137
|
+
|
|
138
|
+
console.log(result.output)
|
|
139
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { validateAndReportEnv } from '../../src/env/env-validation.js'
|
|
3
|
+
|
|
4
|
+
describe('validateAndReportEnv', () => {
|
|
5
|
+
const schema = {
|
|
6
|
+
FOO: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
BAR: {
|
|
11
|
+
type: 'number',
|
|
12
|
+
required: true,
|
|
13
|
+
int: true,
|
|
14
|
+
},
|
|
15
|
+
SECRET: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
required: true,
|
|
18
|
+
secret: true,
|
|
19
|
+
},
|
|
20
|
+
OPTIONAL: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
required: false,
|
|
23
|
+
default: 'hello',
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
it('returns success result when all variables are valid', () => {
|
|
28
|
+
const env = {
|
|
29
|
+
FOO: 'test',
|
|
30
|
+
BAR: '42',
|
|
31
|
+
SECRET: 'super-secret',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = validateAndReportEnv(schema, env)
|
|
35
|
+
|
|
36
|
+
expect(result.success).toBe(true)
|
|
37
|
+
|
|
38
|
+
expect(result.validation.success).toBe(true)
|
|
39
|
+
expect(result.validation.data).toEqual({
|
|
40
|
+
FOO: 'test',
|
|
41
|
+
BAR: 42,
|
|
42
|
+
SECRET: 'super-secret',
|
|
43
|
+
OPTIONAL: 'hello',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
expect(result.report.success).toBe(true)
|
|
47
|
+
expect(result.report.params).toHaveLength(4)
|
|
48
|
+
|
|
49
|
+
expect(result.output).toContain('Environment variables')
|
|
50
|
+
expect(result.output).toContain('OK')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('returns failure result when required variable is missing', () => {
|
|
54
|
+
const env = {
|
|
55
|
+
BAR: '10',
|
|
56
|
+
SECRET: 'x',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result = validateAndReportEnv(schema, env)
|
|
60
|
+
|
|
61
|
+
expect(result.success).toBe(false)
|
|
62
|
+
|
|
63
|
+
expect(result.validation.success).toBe(false)
|
|
64
|
+
expect(result.validation.summary).toHaveProperty('FOO')
|
|
65
|
+
|
|
66
|
+
const fooParam = result.report.params.find((p) => p.key === 'FOO')
|
|
67
|
+
expect(fooParam.valid).toBe(false)
|
|
68
|
+
expect(fooParam.errors.length).toBeGreaterThan(0)
|
|
69
|
+
|
|
70
|
+
expect(result.output).toContain('ERROR')
|
|
71
|
+
expect(result.output).toContain('FOO')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('masks secret values in output', () => {
|
|
75
|
+
const env = {
|
|
76
|
+
FOO: 'test',
|
|
77
|
+
BAR: '1',
|
|
78
|
+
SECRET: 'dont-print-me',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const mask = () => '***'
|
|
82
|
+
|
|
83
|
+
const result = validateAndReportEnv(schema, env, { mask })
|
|
84
|
+
|
|
85
|
+
const secretParam = result.report.params.find((p) => p.key === 'SECRET')
|
|
86
|
+
|
|
87
|
+
expect(secretParam.displayValue).toBe('***')
|
|
88
|
+
expect(result.output).toContain('***')
|
|
89
|
+
expect(result.output).not.toContain('dont-print-me')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('coerces and validates types correctly', () => {
|
|
93
|
+
const env = {
|
|
94
|
+
FOO: 'abc',
|
|
95
|
+
BAR: '5',
|
|
96
|
+
SECRET: 'x',
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = validateAndReportEnv(schema, env)
|
|
100
|
+
|
|
101
|
+
expect(result.success).toBe(true)
|
|
102
|
+
expect(result.validation.data.BAR).toBe(5)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
@@ -155,3 +155,68 @@ export function formatEnvReport(report: {
|
|
|
155
155
|
errors?: string[]
|
|
156
156
|
}>
|
|
157
157
|
}): string
|
|
158
|
+
/**
|
|
159
|
+
* Validates environment variables end-to-end and builds a full report.
|
|
160
|
+
*
|
|
161
|
+
* This function:
|
|
162
|
+
* - validates values using the schema definition
|
|
163
|
+
* - builds a structured report (including masking secrets)
|
|
164
|
+
* - formats a printable output
|
|
165
|
+
*
|
|
166
|
+
* @param {Record<string, Object>} definition
|
|
167
|
+
* Environment schema definition.
|
|
168
|
+
*
|
|
169
|
+
* @param {Record<string, any>} values
|
|
170
|
+
* Raw environment values (e.g. process.env).
|
|
171
|
+
*
|
|
172
|
+
* @param {{
|
|
173
|
+
* mask?: (value: any) => string
|
|
174
|
+
* }} [options]
|
|
175
|
+
*
|
|
176
|
+
* @returns {{
|
|
177
|
+
* success: boolean,
|
|
178
|
+
* validation: {
|
|
179
|
+
* success: boolean,
|
|
180
|
+
* data?: Record<string, any>,
|
|
181
|
+
* summary?: Record<string, string[]>
|
|
182
|
+
* },
|
|
183
|
+
* report: {
|
|
184
|
+
* success: boolean,
|
|
185
|
+
* params: Array<{
|
|
186
|
+
* key: string,
|
|
187
|
+
* value: any,
|
|
188
|
+
* displayValue: string,
|
|
189
|
+
* secret: boolean,
|
|
190
|
+
* valid: boolean,
|
|
191
|
+
* errors?: string[]
|
|
192
|
+
* }>
|
|
193
|
+
* },
|
|
194
|
+
* output: string
|
|
195
|
+
* }}
|
|
196
|
+
*/
|
|
197
|
+
export function validateAndReportEnv(
|
|
198
|
+
definition: Record<string, any>,
|
|
199
|
+
values: Record<string, any>,
|
|
200
|
+
options?: {
|
|
201
|
+
mask?: (value: any) => string
|
|
202
|
+
},
|
|
203
|
+
): {
|
|
204
|
+
success: boolean
|
|
205
|
+
validation: {
|
|
206
|
+
success: boolean
|
|
207
|
+
data?: Record<string, any>
|
|
208
|
+
summary?: Record<string, string[]>
|
|
209
|
+
}
|
|
210
|
+
report: {
|
|
211
|
+
success: boolean
|
|
212
|
+
params: Array<{
|
|
213
|
+
key: string
|
|
214
|
+
value: any
|
|
215
|
+
displayValue: string
|
|
216
|
+
secret: boolean
|
|
217
|
+
valid: boolean
|
|
218
|
+
errors?: string[]
|
|
219
|
+
}>
|
|
220
|
+
}
|
|
221
|
+
output: string
|
|
222
|
+
}
|