env-safe-check 1.0.0 → 1.0.2
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/README.md +255 -93
- package/dist/index.d.ts +71 -0
- package/dist/index.js +163 -0
- package/package.json +34 -7
- package/.github/workflows/npm-publish.yml +0 -38
- package/scripts/patch-extensions.cjs +0 -32
- package/scripts/patch-extensions.js +0 -38
- package/src/index.ts +0 -1
- package/src/validate.ts +0 -20
- package/tsconfig.json +0 -46
package/README.md
CHANGED
|
@@ -1,93 +1,255 @@
|
|
|
1
|
-
# env-safe-check
|
|
2
|
-
|
|
3
|
-
Reliable, minimal utility to validate required environment variables at runtime.
|
|
4
|
-
|
|
5
|
-
This tiny library helps Node and TypeScript projects fail fast with a clear
|
|
6
|
-
error message when required environment variables are missing or empty.
|
|
7
|
-
|
|
8
|
-
**Highlights**
|
|
9
|
-
|
|
10
|
-
- Zero runtime dependencies
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
##
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
1
|
+
# env-safe-check
|
|
2
|
+
|
|
3
|
+
Reliable, minimal utility to validate required environment variables at runtime.
|
|
4
|
+
|
|
5
|
+
This tiny library helps Node and TypeScript projects fail fast with a clear
|
|
6
|
+
error message when required environment variables are missing or empty.
|
|
7
|
+
|
|
8
|
+
**Highlights**
|
|
9
|
+
|
|
10
|
+
- Zero runtime dependencies
|
|
11
|
+
- Two APIs: simple (legacy array) or powerful (schema-based)
|
|
12
|
+
- Type-safe: automatic parsing for `string`, `number`, `boolean`, `json`
|
|
13
|
+
- Custom validators: add your own validation logic
|
|
14
|
+
- Defaults & optional vars: sensible config for each variable
|
|
15
|
+
- Colorful error messages with variable descriptions
|
|
16
|
+
- Works in TypeScript and JavaScript projects (ESM)
|
|
17
|
+
- Safe defaults for CI and production (exits with non-zero code on errors)
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Install from npm (devs using this repo can also build locally):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install env-safe-check
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For local development (this repository):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install
|
|
31
|
+
npm run build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick usage
|
|
35
|
+
|
|
36
|
+
### Simple mode (Legacy)
|
|
37
|
+
|
|
38
|
+
Validate a list of required environment variables:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { validateEnv } from 'env-safe-check';
|
|
42
|
+
|
|
43
|
+
// Exits with code 1 if any are missing
|
|
44
|
+
validateEnv(['DATABASE_URL', 'API_KEY']);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Schema mode (Recommended)
|
|
48
|
+
|
|
49
|
+
Validate with types, defaults, custom validators, and descriptions:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { validateEnv, type VariableSchema } from 'env-safe-check';
|
|
53
|
+
|
|
54
|
+
const env = validateEnv({
|
|
55
|
+
schema: {
|
|
56
|
+
DATABASE_URL: { type: 'string', description: 'PostgreSQL connection URL' },
|
|
57
|
+
PORT: { type: 'number', default: '3000' },
|
|
58
|
+
DEBUG: { type: 'boolean', default: 'false', required: false },
|
|
59
|
+
NODE_ENV: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
validator: (value) =>
|
|
62
|
+
['development', 'production', 'test'].includes(value)
|
|
63
|
+
? true
|
|
64
|
+
: 'Must be one of: development, production, test',
|
|
65
|
+
},
|
|
66
|
+
FEATURE_CONFIG: { type: 'json', required: false },
|
|
67
|
+
},
|
|
68
|
+
throwError: false, // default: exits process on error
|
|
69
|
+
silent: false, // default: prints colorful output
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Returns parsed and validated env object:
|
|
73
|
+
// { DATABASE_URL: string, PORT: number, DEBUG: boolean, NODE_ENV: string, FEATURE_CONFIG?: any }
|
|
74
|
+
console.log(env.PORT); // 3000 (or parsed value from process.env.PORT)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API
|
|
78
|
+
|
|
79
|
+
### `validateEnv(required: string[]): void` (Legacy)
|
|
80
|
+
|
|
81
|
+
- **required** — array of environment variable names to verify
|
|
82
|
+
- **Returns** — void; exits process (code 1) if any are missing or empty
|
|
83
|
+
- **Console output** — colorful error/success messages
|
|
84
|
+
|
|
85
|
+
### `validateEnv(config: ValidateEnvOptions): Record<string, any>` (Recommended)
|
|
86
|
+
|
|
87
|
+
- **config.schema** — Record of variable names to schema definitions
|
|
88
|
+
- Each value can be:
|
|
89
|
+
- A type string shorthand: `'string' | 'number' | 'boolean' | 'json'`
|
|
90
|
+
- A full `VariableSchema` object (see below)
|
|
91
|
+
- **config.throwError** (default: `false`) — throw `EnvValidationError` instead of exiting process
|
|
92
|
+
- **config.silent** (default: `false`) — suppress console output
|
|
93
|
+
- **Returns** — `Record<string, any>` with parsed env variables
|
|
94
|
+
- **Throws** — `EnvValidationError` if `throwError: true` and validation fails
|
|
95
|
+
|
|
96
|
+
### `VariableSchema`
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
interface VariableSchema {
|
|
100
|
+
// Type of the variable. Parsed and validated accordingly.
|
|
101
|
+
// 'string' | 'number' | 'boolean' | 'json'
|
|
102
|
+
type?: 'string' | 'number' | 'boolean' | 'json';
|
|
103
|
+
|
|
104
|
+
// Whether this variable is required (default: true)
|
|
105
|
+
required?: boolean;
|
|
106
|
+
|
|
107
|
+
// Default value if not set and not required
|
|
108
|
+
default?: string;
|
|
109
|
+
|
|
110
|
+
// Custom validation function
|
|
111
|
+
// Return true if valid, or an error string if invalid
|
|
112
|
+
validator?: (value: string) => boolean | string;
|
|
113
|
+
|
|
114
|
+
// Description for error messages
|
|
115
|
+
description?: string;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `EnvValidationError`
|
|
120
|
+
|
|
121
|
+
Thrown when validation fails with `throwError: true`:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
try {
|
|
125
|
+
const env = validateEnv({ schema: { /* ... */ }, throwError: true });
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err instanceof EnvValidationError) {
|
|
128
|
+
console.error('Missing:', err.missing); // string[]
|
|
129
|
+
console.error('Invalid:', err.invalid); // Record<string, string>
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Examples
|
|
135
|
+
|
|
136
|
+
### Type parsing with defaults
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const env = validateEnv({
|
|
140
|
+
schema: {
|
|
141
|
+
PORT: { type: 'number', default: '3000' },
|
|
142
|
+
TIMEOUT: { type: 'number', required: false },
|
|
143
|
+
ENABLE_CACHE: { type: 'boolean', default: 'true' },
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// PORT is always a number (parsed from env or default)
|
|
148
|
+
// TIMEOUT is optional; undefined if not set
|
|
149
|
+
// ENABLE_CACHE is always a boolean
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Custom validators
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
const env = validateEnv({
|
|
156
|
+
schema: {
|
|
157
|
+
WORKER_THREADS: {
|
|
158
|
+
type: 'number',
|
|
159
|
+
validator: (val) => {
|
|
160
|
+
const num = Number(val);
|
|
161
|
+
if (num < 1 || num > 32) {
|
|
162
|
+
return 'Must be between 1 and 32';
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Throw errors instead of exiting
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
try {
|
|
175
|
+
const env = validateEnv({
|
|
176
|
+
schema: { DB_URL: 'string' },
|
|
177
|
+
throwError: true,
|
|
178
|
+
});
|
|
179
|
+
} catch (err) {
|
|
180
|
+
if (err instanceof EnvValidationError) {
|
|
181
|
+
// Handle custom error; code continues (doesn't exit)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Project specifics (for contributors / package authors)
|
|
187
|
+
|
|
188
|
+
- Source files are TypeScript in `src/`.
|
|
189
|
+
- Build emits ESM JavaScript into `dist/`.
|
|
190
|
+
- Source imports are written without `.js` extensions for ergonomics in
|
|
191
|
+
TypeScript; the build process rewrites emitted imports to include `.js`
|
|
192
|
+
so the output is runnable under Node ESM (`node >= 12` with ESM support).
|
|
193
|
+
|
|
194
|
+
Commands:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Build and patch emitted imports
|
|
198
|
+
npm run build
|
|
199
|
+
|
|
200
|
+
# Run TypeScript type-check only
|
|
201
|
+
npx tsc --noEmit
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Publishing
|
|
205
|
+
|
|
206
|
+
1. Bump the package version in `package.json`.
|
|
207
|
+
2. Run `npm run build` to produce `dist/`.
|
|
208
|
+
3. Verify the `main`/`exports` fields point to built files (if applicable).
|
|
209
|
+
4. `npm publish --access public`
|
|
210
|
+
|
|
211
|
+
## Contributing
|
|
212
|
+
|
|
213
|
+
Contributions are welcome. Open issues for bugs or small feature requests.
|
|
214
|
+
|
|
215
|
+
Please run the tests (if added) and ensure linting/type-checks pass before submitting a PR.
|
|
216
|
+
|
|
217
|
+
### Conventional Commits
|
|
218
|
+
|
|
219
|
+
This project uses [semantic-release](https://semantic-release.gitbook.io/) to automate versioning and publishing based on commit messages. Please follow the [Conventional Commits](https://www.conventionalcommits.org/) format:
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
type(scope): subject
|
|
224
|
+
|
|
225
|
+
body
|
|
226
|
+
|
|
227
|
+
footer
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Types:**
|
|
231
|
+
- `feat:` A new feature (bumps minor version)
|
|
232
|
+
- `fix:` A bug fix (bumps patch version)
|
|
233
|
+
- `docs:` Documentation changes
|
|
234
|
+
- `style:` Code style changes (no logic changes)
|
|
235
|
+
- `refactor:` Refactor code without changing behavior
|
|
236
|
+
- `perf:` Performance improvements
|
|
237
|
+
- `test:` Adding/updating tests
|
|
238
|
+
- `ci:` CI/CD changes
|
|
239
|
+
- `chore:` Build, dependencies, etc.
|
|
240
|
+
|
|
241
|
+
**Breaking Changes:**
|
|
242
|
+
- Add `BREAKING CHANGE: description` in the footer to bump major version
|
|
243
|
+
- Or use `feat!:` in the type to indicate a breaking change
|
|
244
|
+
|
|
245
|
+
**Examples:**
|
|
246
|
+
```bash
|
|
247
|
+
npm run cz # Interactive prompt (recommended)
|
|
248
|
+
git commit -m "feat: add JSON type validation"
|
|
249
|
+
git commit -m "fix: correct boolean parsing"
|
|
250
|
+
git commit -m "feat!: change API to return parsed object"
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variable type for validation and parsing.
|
|
3
|
+
*/
|
|
4
|
+
type VariableType = 'string' | 'number' | 'boolean' | 'json';
|
|
5
|
+
/**
|
|
6
|
+
* Schema definition for a single environment variable.
|
|
7
|
+
*/
|
|
8
|
+
interface VariableSchema {
|
|
9
|
+
/**
|
|
10
|
+
* Type of the variable. Parsed and validated accordingly.
|
|
11
|
+
* @default 'string'
|
|
12
|
+
*/
|
|
13
|
+
type?: VariableType;
|
|
14
|
+
/**
|
|
15
|
+
* Whether this variable is required.
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
required?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Default value if the variable is not set (and not required).
|
|
21
|
+
*/
|
|
22
|
+
default?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Custom validator function. Return true if valid, or an error message string if invalid.
|
|
25
|
+
*/
|
|
26
|
+
validator?: (value: string) => boolean | string;
|
|
27
|
+
/**
|
|
28
|
+
* Description of the variable (for error messages).
|
|
29
|
+
*/
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Options for validateEnv function.
|
|
34
|
+
*/
|
|
35
|
+
interface ValidateEnvOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Schema defining required/optional vars, types, defaults, and validators.
|
|
38
|
+
*/
|
|
39
|
+
schema: Record<string, VariableSchema | VariableType>;
|
|
40
|
+
/**
|
|
41
|
+
* If true, throw a custom error instead of calling process.exit(1).
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
throwError?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* If true, don't print console messages.
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
silent?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Custom error thrown when validation fails (if throwError option is true).
|
|
53
|
+
*/
|
|
54
|
+
declare class EnvValidationError extends Error {
|
|
55
|
+
readonly missing: string[];
|
|
56
|
+
readonly invalid: Record<string, string>;
|
|
57
|
+
constructor(message: string, missing?: string[], invalid?: Record<string, string>);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate environment variables against a schema with type checking, defaults, and custom validators.
|
|
62
|
+
*
|
|
63
|
+
* @overload
|
|
64
|
+
* function validateEnv(config: ValidateEnvOptions): Record<string, any>
|
|
65
|
+
*
|
|
66
|
+
* @overload
|
|
67
|
+
* function validateEnv(required: string[]): void
|
|
68
|
+
*/
|
|
69
|
+
declare function validateEnv(configOrRequired: ValidateEnvOptions | string[]): Record<string, any> | void;
|
|
70
|
+
|
|
71
|
+
export { EnvValidationError, ValidateEnvOptions, VariableSchema, VariableType, validateEnv };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var EnvValidationError = class extends Error {
|
|
3
|
+
constructor(message, missing = [], invalid = {}) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.missing = missing;
|
|
6
|
+
this.invalid = invalid;
|
|
7
|
+
this.name = "EnvValidationError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/validate.ts
|
|
12
|
+
var colors = {
|
|
13
|
+
reset: "\x1B[0m",
|
|
14
|
+
bold: "\x1B[1m",
|
|
15
|
+
red: "\x1B[31m",
|
|
16
|
+
green: "\x1B[32m",
|
|
17
|
+
yellow: "\x1B[33m",
|
|
18
|
+
cyan: "\x1B[36m",
|
|
19
|
+
gray: "\x1B[90m"
|
|
20
|
+
};
|
|
21
|
+
function parseValue(value, type) {
|
|
22
|
+
switch (type) {
|
|
23
|
+
case "number":
|
|
24
|
+
const num = Number(value);
|
|
25
|
+
if (isNaN(num))
|
|
26
|
+
throw new Error("Invalid number");
|
|
27
|
+
return num;
|
|
28
|
+
case "boolean":
|
|
29
|
+
if (value.toLowerCase() === "true" || value === "1")
|
|
30
|
+
return true;
|
|
31
|
+
if (value.toLowerCase() === "false" || value === "0")
|
|
32
|
+
return false;
|
|
33
|
+
throw new Error("Invalid boolean (expected 'true', 'false', '1', or '0')");
|
|
34
|
+
case "json":
|
|
35
|
+
return JSON.parse(value);
|
|
36
|
+
case "string":
|
|
37
|
+
default:
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function validateEnv(configOrRequired) {
|
|
42
|
+
if (Array.isArray(configOrRequired)) {
|
|
43
|
+
validateEnvLegacy(configOrRequired);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const config = configOrRequired;
|
|
47
|
+
const { schema, throwError = false, silent = false } = config;
|
|
48
|
+
const missing = [];
|
|
49
|
+
const invalid = {};
|
|
50
|
+
const parsed = {};
|
|
51
|
+
for (const [key, schemaDef] of Object.entries(schema)) {
|
|
52
|
+
const varSchema = typeof schemaDef === "string" ? { type: schemaDef } : schemaDef;
|
|
53
|
+
const isRequired = varSchema.required !== false;
|
|
54
|
+
const type = varSchema.type || "string";
|
|
55
|
+
const value = process.env[key];
|
|
56
|
+
if (value === void 0 || value.trim() === "") {
|
|
57
|
+
if (isRequired) {
|
|
58
|
+
missing.push(key);
|
|
59
|
+
} else if (varSchema.default !== void 0) {
|
|
60
|
+
try {
|
|
61
|
+
parsed[key] = parseValue(varSchema.default, type);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
invalid[key] = `Default value invalid: ${err.message}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
parsed[key] = parseValue(value, type);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
invalid[key] = `${err.message}`;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (varSchema.validator) {
|
|
75
|
+
const validationResult = varSchema.validator(value);
|
|
76
|
+
if (validationResult !== true) {
|
|
77
|
+
const errorMsg = typeof validationResult === "string" ? validationResult : "Validation failed";
|
|
78
|
+
invalid[key] = errorMsg;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const hasErrors = missing.length > 0 || Object.keys(invalid).length > 0;
|
|
83
|
+
if (hasErrors) {
|
|
84
|
+
const errorMsg = buildErrorMessage(missing, invalid, schema);
|
|
85
|
+
if (!silent) {
|
|
86
|
+
console.error(errorMsg);
|
|
87
|
+
}
|
|
88
|
+
if (throwError) {
|
|
89
|
+
throw new EnvValidationError(
|
|
90
|
+
"Environment variable validation failed",
|
|
91
|
+
missing,
|
|
92
|
+
invalid
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!silent) {
|
|
99
|
+
console.log(`${colors.green}\u2705 All environment variables are valid.${colors.reset}`);
|
|
100
|
+
}
|
|
101
|
+
return parsed;
|
|
102
|
+
}
|
|
103
|
+
function validateEnvLegacy(required) {
|
|
104
|
+
const missing = required.filter((key) => {
|
|
105
|
+
const value = process.env[key];
|
|
106
|
+
return value === void 0 || value.trim() === "";
|
|
107
|
+
});
|
|
108
|
+
if (missing.length > 0) {
|
|
109
|
+
console.error(
|
|
110
|
+
`${colors.red}${colors.bold}\u274C Missing required environment variables:${colors.reset}
|
|
111
|
+
`
|
|
112
|
+
);
|
|
113
|
+
missing.forEach((key) => {
|
|
114
|
+
console.error(`${colors.yellow}- ${key}${colors.reset}`);
|
|
115
|
+
});
|
|
116
|
+
console.error(
|
|
117
|
+
`
|
|
118
|
+
${colors.cyan}Tip:${colors.reset} define them in your .env file or environment config.`
|
|
119
|
+
);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
console.log(
|
|
123
|
+
`${colors.green}\u2705 All required environment variables are present.${colors.reset}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
function buildErrorMessage(missing, invalid, schema) {
|
|
127
|
+
let msg = `${colors.red}${colors.bold}\u274C Environment validation failed${colors.reset}
|
|
128
|
+
`;
|
|
129
|
+
if (missing.length > 0) {
|
|
130
|
+
msg += `
|
|
131
|
+
${colors.bold}Missing required variables:${colors.reset}
|
|
132
|
+
`;
|
|
133
|
+
missing.forEach((key) => {
|
|
134
|
+
const desc = getSchemaDescription(key, schema);
|
|
135
|
+
msg += ` ${colors.yellow}${key}${colors.reset}${desc ? ` - ${colors.gray}${desc}${colors.reset}` : ""}
|
|
136
|
+
`;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (Object.keys(invalid).length > 0) {
|
|
140
|
+
msg += `
|
|
141
|
+
${colors.bold}Invalid variables:${colors.reset}
|
|
142
|
+
`;
|
|
143
|
+
for (const [key, error] of Object.entries(invalid)) {
|
|
144
|
+
msg += ` ${colors.yellow}${key}${colors.reset}: ${error}
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
msg += `
|
|
149
|
+
${colors.cyan}Tip:${colors.reset} define/fix them in your .env file or environment config.`;
|
|
150
|
+
return msg;
|
|
151
|
+
}
|
|
152
|
+
function getSchemaDescription(key, schema) {
|
|
153
|
+
const def = schema[key];
|
|
154
|
+
if (!def)
|
|
155
|
+
return "";
|
|
156
|
+
if (typeof def === "string")
|
|
157
|
+
return "";
|
|
158
|
+
return def.description || "";
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
EnvValidationError,
|
|
162
|
+
validateEnv
|
|
163
|
+
};
|
package/package.json
CHANGED
|
@@ -1,18 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "env-safe-check",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
6
10
|
"scripts": {
|
|
7
11
|
"test": "echo \"hello world\"",
|
|
8
|
-
"build": "
|
|
12
|
+
"build": "tsup src/index.ts --format esm --dts --out-dir dist",
|
|
13
|
+
"prepare": "npm run build && husky install",
|
|
14
|
+
"cz": "cz"
|
|
9
15
|
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
|
|
16
|
+
"keywords": [
|
|
17
|
+
"env",
|
|
18
|
+
"environment",
|
|
19
|
+
"validation",
|
|
20
|
+
"safe",
|
|
21
|
+
"check",
|
|
22
|
+
"typescript"
|
|
23
|
+
],
|
|
24
|
+
"author": "Pushpak Kurella",
|
|
12
25
|
"license": "ISC",
|
|
13
26
|
"description": "",
|
|
14
27
|
"devDependencies": {
|
|
15
|
-
"
|
|
16
|
-
"@
|
|
28
|
+
"@commitlint/config-conventional": "^20.4.1",
|
|
29
|
+
"@semantic-release/github": "^12.0.6",
|
|
30
|
+
"@semantic-release/npm": "^13.1.4",
|
|
31
|
+
"@types/node": "^20.6.5",
|
|
32
|
+
"commitizen": "^4.3.1",
|
|
33
|
+
"commitlint": "^20.4.1",
|
|
34
|
+
"cz-conventional-changelog": "^3.3.0",
|
|
35
|
+
"husky": "^9.1.7",
|
|
36
|
+
"semantic-release": "^25.0.3",
|
|
37
|
+
"tsup": "^6.6.0",
|
|
38
|
+
"typescript": "^5.9.3"
|
|
39
|
+
},
|
|
40
|
+
"config": {
|
|
41
|
+
"commitizen": {
|
|
42
|
+
"path": "./node_modules/cz-conventional-changelog"
|
|
43
|
+
}
|
|
17
44
|
}
|
|
18
45
|
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: Node.js Package
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
release:
|
|
8
|
-
types: [created]
|
|
9
|
-
workflow_dispatch: {}
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
build:
|
|
13
|
-
# Only run automatically for the `main` branch or when a release is created.
|
|
14
|
-
if: github.ref == 'refs/heads/main' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v4
|
|
18
|
-
- uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: 20
|
|
21
|
-
- run: npm ci
|
|
22
|
-
- run: npm test
|
|
23
|
-
|
|
24
|
-
publish-npm:
|
|
25
|
-
needs: build
|
|
26
|
-
# Only publish when the run is for `main` or a release (keeps publishes scoped to main).
|
|
27
|
-
if: github.ref == 'refs/heads/main' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
|
28
|
-
runs-on: ubuntu-latest
|
|
29
|
-
steps:
|
|
30
|
-
- uses: actions/checkout@v4
|
|
31
|
-
- uses: actions/setup-node@v4
|
|
32
|
-
with:
|
|
33
|
-
node-version: 20
|
|
34
|
-
registry-url: https://registry.npmjs.org/
|
|
35
|
-
- run: npm ci
|
|
36
|
-
- run: npm publish
|
|
37
|
-
env:
|
|
38
|
-
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
function walk(dir) {
|
|
5
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
6
|
-
for (const entry of entries) {
|
|
7
|
-
const full = path.join(dir, entry.name);
|
|
8
|
-
if (entry.isDirectory()) walk(full);
|
|
9
|
-
else if (entry.isFile() && full.endsWith('.js')) patchFile(full);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function patchFile(file) {
|
|
14
|
-
let src = fs.readFileSync(file, 'utf8');
|
|
15
|
-
src = src.replace(/(from\s+|import\()(["'])(\.\/[^"'\)]+?)\2/g, (m, p1, q, p2) => {
|
|
16
|
-
if (/\.[a-zA-Z0-9]+$/.test(p2)) return m;
|
|
17
|
-
return `${p1}${q}${p2}.js${q}`;
|
|
18
|
-
});
|
|
19
|
-
src = src.replace(/(export\s+[^;]*?from\s+)(["'])(\.\/[^"']+?)\2/g, (m, p1, q, p2) => {
|
|
20
|
-
if (/\.[a-zA-Z0-9]+$/.test(p2)) return m;
|
|
21
|
-
return `${p1}${q}${p2}.js${q}`;
|
|
22
|
-
});
|
|
23
|
-
fs.writeFileSync(file, src, 'utf8');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const dist = path.join(__dirname, '..', 'dist');
|
|
27
|
-
if (!fs.existsSync(dist)) {
|
|
28
|
-
console.error('dist directory not found; run tsc first');
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
walk(dist);
|
|
32
|
-
console.log('Patched imports to include .js extensions in', dist);
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
function walk(dir) {
|
|
5
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
6
|
-
for (const entry of entries) {
|
|
7
|
-
const full = path.join(dir, entry.name);
|
|
8
|
-
if (entry.isDirectory()) walk(full);
|
|
9
|
-
else if (entry.isFile() && full.endsWith('.js')) patchFile(full);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function patchFile(file) {
|
|
14
|
-
let src = fs.readFileSync(file, 'utf8');
|
|
15
|
-
// Replace import/export specifiers that are relative and have no extension
|
|
16
|
-
// Examples: import {x} from "./foo" -> ./foo.js
|
|
17
|
-
// export * from './bar' -> ./bar.js
|
|
18
|
-
src = src.replace(/(from\s+|import\()(["'])(\.\/[^"'\)]+?)\2/g, (m, p1, q, p2) => {
|
|
19
|
-
// p2 is like ./module or ../module/sub
|
|
20
|
-
// If it already ends with an extension, leave it
|
|
21
|
-
if (/\.[a-zA-Z0-9]+$/.test(p2)) return m;
|
|
22
|
-
return `${p1}${q}${p2}.js${q}`;
|
|
23
|
-
});
|
|
24
|
-
// also handle export ... from '...'
|
|
25
|
-
src = src.replace(/(export\s+[^;]*?from\s+)(["'])(\.\/[^"']+?)\2/g, (m, p1, q, p2) => {
|
|
26
|
-
if (/\.[a-zA-Z0-9]+$/.test(p2)) return m;
|
|
27
|
-
return `${p1}${q}${p2}.js${q}`;
|
|
28
|
-
});
|
|
29
|
-
fs.writeFileSync(file, src, 'utf8');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const dist = path.join(__dirname, '..', 'dist');
|
|
33
|
-
if (!fs.existsSync(dist)) {
|
|
34
|
-
console.error('dist directory not found; run tsc first');
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
walk(dist);
|
|
38
|
-
console.log('Patched imports to include .js extensions in', dist);
|
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { validateEnv } from "./validate";
|
package/src/validate.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export function validateEnv(required: string[]): void {
|
|
2
|
-
const missing = required.filter((key) => {
|
|
3
|
-
const value = process.env[key];
|
|
4
|
-
return value === undefined || value.trim() === "";
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
if (missing.length > 0) {
|
|
8
|
-
console.error("❌ Missing required environment variables:\n");
|
|
9
|
-
|
|
10
|
-
missing.forEach((key) => {
|
|
11
|
-
console.error(`- ${key}`);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
console.error(
|
|
15
|
-
"\nPlease define them in your .env file or environment config."
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
// File Layout
|
|
5
|
-
// "rootDir": "./src",
|
|
6
|
-
// "outDir": "./dist",
|
|
7
|
-
|
|
8
|
-
// Environment Settings
|
|
9
|
-
// See also https://aka.ms/tsconfig/module
|
|
10
|
-
"module": "esnext",
|
|
11
|
-
"moduleResolution": "node",
|
|
12
|
-
"target": "esnext",
|
|
13
|
-
"outDir": "./dist",
|
|
14
|
-
"types": ["node"],
|
|
15
|
-
// For nodejs:
|
|
16
|
-
// "lib": ["esnext"],
|
|
17
|
-
// "types": ["node"],
|
|
18
|
-
// and npm install -D @types/node
|
|
19
|
-
|
|
20
|
-
// Other Outputs
|
|
21
|
-
"sourceMap": true,
|
|
22
|
-
"declaration": true,
|
|
23
|
-
"declarationMap": true,
|
|
24
|
-
|
|
25
|
-
// Stricter Typechecking Options
|
|
26
|
-
"noUncheckedIndexedAccess": true,
|
|
27
|
-
"exactOptionalPropertyTypes": true,
|
|
28
|
-
|
|
29
|
-
// Style Options
|
|
30
|
-
// "noImplicitReturns": true,
|
|
31
|
-
// "noImplicitOverride": true,
|
|
32
|
-
// "noUnusedLocals": true,
|
|
33
|
-
// "noUnusedParameters": true,
|
|
34
|
-
// "noFallthroughCasesInSwitch": true,
|
|
35
|
-
// "noPropertyAccessFromIndexSignature": true,
|
|
36
|
-
|
|
37
|
-
// Recommended Options
|
|
38
|
-
"strict": true,
|
|
39
|
-
"jsx": "react-jsx",
|
|
40
|
-
"verbatimModuleSyntax": true,
|
|
41
|
-
"isolatedModules": true,
|
|
42
|
-
"noUncheckedSideEffectImports": true,
|
|
43
|
-
"moduleDetection": "force",
|
|
44
|
-
"skipLibCheck": true,
|
|
45
|
-
}
|
|
46
|
-
}
|