dotenv-gad 0.1.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/LICENSE +21 -0
- package/README.md +6 -3
- package/dist/cli/index.js +10 -1
- package/dist/index.js +5 -2
- package/dist/types/cli/commands/check.d.ts +2 -0
- package/dist/types/cli/commands/docs.d.ts +2 -0
- package/dist/types/cli/commands/fix.d.ts +2 -0
- package/dist/types/cli/commands/init.d.ts +2 -0
- package/dist/types/cli/commands/sync.d.ts +2 -0
- package/dist/types/cli/commands/types.d.ts +2 -0
- package/dist/types/cli/commands/utils.d.ts +20 -0
- package/dist/types/cli/index.d.ts +5 -0
- package/dist/types/compose.d.ts +22 -0
- package/dist/types/errors.d.ts +22 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/schema.d.ts +35 -0
- package/dist/{utils.js → types/utils.d.ts} +5 -18
- package/dist/types/validator.d.ts +12 -0
- package/package.json +3 -8
- package/dist/cli/commands/check.js +0 -41
- package/dist/cli/commands/init.js +0 -61
- package/dist/cli/commands/sync.js +0 -37
- package/dist/cli/commands/types.js +0 -40
- package/dist/cli/commands/utils.js +0 -40
- package/dist/errors.js +0 -22
- package/dist/schema.js +0 -10
- package/dist/validator.js +0 -180
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kasim Lyee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# dotenv-gad
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/js/dotenv-
|
|
3
|
+
[](https://badge.fury.io/js/dotenv-gad)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
6
|
**dotenv-gad** is an environment variable validation tool that brings type safety and schema validation to your Node.js and JavaScript applications. It extends `dotenv` with features like:
|
|
7
7
|
|
|
8
8
|
- Type-safe environment variables
|
|
9
9
|
- Schema validation
|
|
10
|
+
- Schema composition
|
|
10
11
|
- Automatic documentation generation
|
|
11
12
|
- TypeScript support
|
|
12
13
|
- CLI tooling
|
|
@@ -61,6 +62,8 @@ console.log(`Server running on port ${env.PORT}`);
|
|
|
61
62
|
| `sync` | Generate/update .env.example |
|
|
62
63
|
| `types` | Generate env.d.ts TypeScript types |
|
|
63
64
|
| `init` | Create starter schema |
|
|
65
|
+
| `fix` | Fixes environment issues |
|
|
66
|
+
| `docs` | Generates .env documentation |
|
|
64
67
|
|
|
65
68
|
```bash
|
|
66
69
|
npx dotenv-gad check
|
|
@@ -195,8 +198,8 @@ Environment validation failed:
|
|
|
195
198
|
}
|
|
196
199
|
```
|
|
197
200
|
|
|
198
|
-
##
|
|
201
|
+
## License
|
|
199
202
|
|
|
200
203
|
MIT © [Kasim Lyee]
|
|
201
204
|
|
|
202
|
-
|
|
205
|
+
[] are welcome!!
|
package/dist/cli/index.js
CHANGED
|
@@ -9,6 +9,8 @@ import checkCommand from "./commands/check.js";
|
|
|
9
9
|
import syncCommand from "./commands/sync.js";
|
|
10
10
|
import typesCommand from "./commands/types.js";
|
|
11
11
|
import initCommand from "./commands/init.js";
|
|
12
|
+
import fixCommand from "./commands/fix.js";
|
|
13
|
+
import docsCommand from "./commands/docs.js";
|
|
12
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
15
|
const pkg = JSON.parse(readFileSync(join(__dirname, "../../package.json"), "utf-8"));
|
|
14
16
|
export function createCLI() {
|
|
@@ -22,7 +24,14 @@ export function createCLI() {
|
|
|
22
24
|
.option("--debug", "Enable debug output")
|
|
23
25
|
.option("--env <file>", "Specify env file path", ".env")
|
|
24
26
|
.option("--schema <file>", "Specify schema file path", "env.schema.ts");
|
|
25
|
-
const commands = [
|
|
27
|
+
const commands = [
|
|
28
|
+
checkCommand,
|
|
29
|
+
syncCommand,
|
|
30
|
+
typesCommand,
|
|
31
|
+
initCommand,
|
|
32
|
+
fixCommand,
|
|
33
|
+
docsCommand,
|
|
34
|
+
];
|
|
26
35
|
commands.forEach((command) => {
|
|
27
36
|
const cmd = command(program);
|
|
28
37
|
program.addCommand(cmd);
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { EnvValidator } from "./validator.js";
|
|
2
2
|
import { defineSchema } from "./schema.js";
|
|
3
|
-
import { AggregateError } from "./errors.js";
|
|
3
|
+
import { AggregateError, EnvValidationError } from "./errors.js";
|
|
4
4
|
import { loadEnv, createEnvProxy } from "./utils.js";
|
|
5
|
+
import { composeSchema } from "./compose.js";
|
|
6
|
+
import loadSchema from "./cli/commands/types.js";
|
|
7
|
+
import { applyFix } from "./cli/commands/utils.js";
|
|
5
8
|
import dotenv from "dotenv";
|
|
6
|
-
export { defineSchema, AggregateError, EnvValidator, loadEnv, createEnvProxy };
|
|
9
|
+
export { defineSchema, AggregateError, EnvValidationError, EnvValidator, loadEnv, createEnvProxy, composeSchema, loadSchema, applyFix, };
|
|
7
10
|
export function validateEnv(schema, options) {
|
|
8
11
|
const env = dotenv.config().parsed || {};
|
|
9
12
|
const validator = new EnvValidator(schema, options);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SchemaDefinition } from "../../schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Loads a schema from a file.
|
|
4
|
+
* @param schemaPath Path to the schema file.
|
|
5
|
+
* @returns The loaded schema.
|
|
6
|
+
* @throws If the schema file is malformed or cannot be loaded.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadSchema(schemaPath: string): Promise<SchemaDefinition>;
|
|
9
|
+
/**
|
|
10
|
+
* Applies fixes to the given environment file by prompting the user to input
|
|
11
|
+
* values for missing variables and variables that do not match the schema.
|
|
12
|
+
*
|
|
13
|
+
* @param issues An object where each key is the name of an environment variable
|
|
14
|
+
* and the value is an object containing the `value` property (which is the
|
|
15
|
+
* invalid value) and the `key` property (which is the name of the variable).
|
|
16
|
+
* @param schema The schema definition for the environment variables.
|
|
17
|
+
* @param envPath The path to the environment file. Defaults to `.env`.
|
|
18
|
+
* @throws If the schema is malformed or if the user cancels the prompt.
|
|
19
|
+
*/
|
|
20
|
+
export declare function applyFix(issues: Record<string, any>, schema: SchemaDefinition, envPath?: string): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SchemaDefinition } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compose multiple schema definitions into a single one.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const dbSchema = {
|
|
7
|
+
* DB_HOST: { type: "string" },
|
|
8
|
+
* DB_USER: { type: "string" },
|
|
9
|
+
* DB_PASSWORD: { type: "string", sensitive: true },
|
|
10
|
+
* };
|
|
11
|
+
*
|
|
12
|
+
* const appSchema = {
|
|
13
|
+
* APP_NAME: { type: "string" },
|
|
14
|
+
* APP_PORT: { type: "number" },
|
|
15
|
+
* };
|
|
16
|
+
*
|
|
17
|
+
* const fullSchema = composeSchema(dbSchema, appSchema);
|
|
18
|
+
*
|
|
19
|
+
* @param {...SchemaDefinition[]} schemas - schema definitions to compose
|
|
20
|
+
* @returns {SchemaDefinition} - a single schema definition with all the properties
|
|
21
|
+
*/
|
|
22
|
+
export declare function composeSchema(...schemas: SchemaDefinition[]): SchemaDefinition;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SchemaRule } from "./schema.js";
|
|
2
|
+
export declare class EnvValidationError extends Error {
|
|
3
|
+
key: string;
|
|
4
|
+
message: string;
|
|
5
|
+
receiveValue?: unknown | undefined;
|
|
6
|
+
constructor(key: string, message: string, receiveValue?: unknown | undefined);
|
|
7
|
+
}
|
|
8
|
+
export declare class AggregateError extends Error {
|
|
9
|
+
errors: {
|
|
10
|
+
key: string;
|
|
11
|
+
message: string;
|
|
12
|
+
value?: any;
|
|
13
|
+
rule?: SchemaRule;
|
|
14
|
+
}[];
|
|
15
|
+
constructor(errors: {
|
|
16
|
+
key: string;
|
|
17
|
+
message: string;
|
|
18
|
+
value?: any;
|
|
19
|
+
rule?: SchemaRule;
|
|
20
|
+
}[], message: string);
|
|
21
|
+
toString(): string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EnvValidator } from "./validator.js";
|
|
2
|
+
import { defineSchema, SchemaDefinition, SchemaRule } from "./schema.js";
|
|
3
|
+
import { AggregateError, EnvValidationError } from "./errors.js";
|
|
4
|
+
import { loadEnv, createEnvProxy } from "./utils.js";
|
|
5
|
+
import { composeSchema } from "./compose.js";
|
|
6
|
+
import loadSchema from "./cli/commands/types.js";
|
|
7
|
+
import { applyFix } from "./cli/commands/utils.js";
|
|
8
|
+
export { defineSchema, AggregateError, EnvValidationError, EnvValidator, loadEnv, createEnvProxy, composeSchema, loadSchema, applyFix, };
|
|
9
|
+
export type { SchemaDefinition, SchemaRule };
|
|
10
|
+
export declare function validateEnv(schema: SchemaDefinition, options?: {
|
|
11
|
+
strict?: boolean;
|
|
12
|
+
}): Record<string, any>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type PrimitiveType = "string" | "number" | "boolean" | "date";
|
|
2
|
+
type ComplexType = "object" | "array" | "email" | "url" | "ip" | "json" | "port";
|
|
3
|
+
type SchemaType = PrimitiveType | ComplexType;
|
|
4
|
+
export interface SchemaRule {
|
|
5
|
+
type: SchemaType;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
default?: any;
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
minLength?: number;
|
|
11
|
+
maxLength?: number;
|
|
12
|
+
validate?: (value: any) => boolean;
|
|
13
|
+
transform?: (value: any) => any;
|
|
14
|
+
sensitive?: boolean;
|
|
15
|
+
docs?: string;
|
|
16
|
+
enum?: any[];
|
|
17
|
+
regex?: RegExp;
|
|
18
|
+
regexError?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
items?: SchemaRule;
|
|
21
|
+
properties?: Record<string, SchemaRule>;
|
|
22
|
+
env?: {
|
|
23
|
+
[envName: string]: Partial<SchemaRule>;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export type SchemaDefinition = Record<string, SchemaRule>;
|
|
27
|
+
/**
|
|
28
|
+
* Define a schema for a set of environment variables.
|
|
29
|
+
*
|
|
30
|
+
* @param schema A record where each key is the name of an environment variable
|
|
31
|
+
* and the value is a `SchemaRule` object that defines the rules for that
|
|
32
|
+
* variable.
|
|
33
|
+
*/
|
|
34
|
+
export declare function defineSchema(schema: SchemaDefinition): SchemaDefinition;
|
|
35
|
+
export {};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { EnvValidator } from "./validator.js";
|
|
1
|
+
import { SchemaDefinition } from "./schema.js";
|
|
3
2
|
/**
|
|
4
3
|
* Load the environment variables from a .env file, validate them against the schema
|
|
5
4
|
* and return an object with the validated values.
|
|
@@ -9,11 +8,9 @@ import { EnvValidator } from "./validator.js";
|
|
|
9
8
|
*
|
|
10
9
|
* @returns A validated object with the environment variables.
|
|
11
10
|
*/
|
|
12
|
-
export function loadEnv(schema, options
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return validator.validate(env);
|
|
16
|
-
}
|
|
11
|
+
export declare function loadEnv(schema: SchemaDefinition, options?: {
|
|
12
|
+
strict?: boolean;
|
|
13
|
+
}): Record<string, any>;
|
|
17
14
|
/**
|
|
18
15
|
* Create a proxy around the validated environment variables. The proxy will
|
|
19
16
|
* throw an error if you try to access a variable that is not validated.
|
|
@@ -23,14 +20,4 @@ export function loadEnv(schema, options) {
|
|
|
23
20
|
* @returns A proxy object that throws an error if you access an
|
|
24
21
|
* unvalidated variable.
|
|
25
22
|
*/
|
|
26
|
-
export function createEnvProxy(validatedEnv)
|
|
27
|
-
return new Proxy(validatedEnv, {
|
|
28
|
-
get(target, prop) {
|
|
29
|
-
const value = target[prop];
|
|
30
|
-
if (value === undefined) {
|
|
31
|
-
throw new Error(`Environment variable ${String(prop)} is not validated`);
|
|
32
|
-
}
|
|
33
|
-
return value;
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
}
|
|
23
|
+
export declare function createEnvProxy<T extends Record<string, any>>(validatedEnv: T): T;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SchemaDefinition } from "./schema.js";
|
|
2
|
+
export declare class EnvValidator {
|
|
3
|
+
private schema;
|
|
4
|
+
private options?;
|
|
5
|
+
private errors;
|
|
6
|
+
constructor(schema: SchemaDefinition, options?: {
|
|
7
|
+
strict?: boolean;
|
|
8
|
+
} | undefined);
|
|
9
|
+
validate(env: Record<string, string | undefined>): Record<string, any>;
|
|
10
|
+
private validateKey;
|
|
11
|
+
private getEffectiveRule;
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dotenv-gad",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -13,12 +13,6 @@
|
|
|
13
13
|
"dev": "tsc --watch",
|
|
14
14
|
"prepublishOnly": "npm run build && npm test"
|
|
15
15
|
},
|
|
16
|
-
"jest": {
|
|
17
|
-
"extensionsToTreatAsEsm": [
|
|
18
|
-
".ts"
|
|
19
|
-
],
|
|
20
|
-
"transform": {}
|
|
21
|
-
},
|
|
22
16
|
"keywords": [
|
|
23
17
|
"dotenv",
|
|
24
18
|
"environment",
|
|
@@ -35,7 +29,7 @@
|
|
|
35
29
|
"./package.json": "./package.json"
|
|
36
30
|
},
|
|
37
31
|
"files": [
|
|
38
|
-
"dist
|
|
32
|
+
"dist/**/*.d.ts",
|
|
39
33
|
"README.md",
|
|
40
34
|
"LICENSE"
|
|
41
35
|
],
|
|
@@ -63,6 +57,7 @@
|
|
|
63
57
|
"ts-jest": "^29.4.0"
|
|
64
58
|
},
|
|
65
59
|
"dependencies": {
|
|
60
|
+
"dotenv": "^16.5.0",
|
|
66
61
|
"esbuild": "^0.25.5"
|
|
67
62
|
}
|
|
68
63
|
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import { validateEnv } from "../../index.js";
|
|
5
|
-
import { AggregateError } from "../../errors.js";
|
|
6
|
-
import { loadSchema } from "./utils.js";
|
|
7
|
-
export default function (program) {
|
|
8
|
-
return new Command("check")
|
|
9
|
-
.description("Validate .env against schema")
|
|
10
|
-
.option("--strict", "Fail on extra env vars not in schema")
|
|
11
|
-
.option("--fix", "Attempt to fix errors interactively")
|
|
12
|
-
.action(async (option, command) => {
|
|
13
|
-
const rootOpts = command.parent.opts();
|
|
14
|
-
const spinner = ora("Validatng environment.......").start();
|
|
15
|
-
try {
|
|
16
|
-
const schema = await loadSchema(rootOpts.schema);
|
|
17
|
-
const env = validateEnv(schema, {
|
|
18
|
-
strict: option.strict,
|
|
19
|
-
});
|
|
20
|
-
spinner.succeed(chalk.green("Environment validation passed!"));
|
|
21
|
-
console.log(chalk.dim(`Found ${Object.keys(env).length} valid variables`));
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
spinner.stop();
|
|
25
|
-
if (error instanceof AggregateError) {
|
|
26
|
-
console.error(chalk.red("\nEnvironment validation failed:"));
|
|
27
|
-
error.errors.forEach((e) => {
|
|
28
|
-
console.log(` ${chalk.yellow(e.key)}: ${e.message}`);
|
|
29
|
-
if (e.rule?.docs) {
|
|
30
|
-
console.log(chalk.dim(` ${e.rule.docs}`));
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
console.error(chalk.red("Unexpected error:"), error);
|
|
37
|
-
process.exit(2);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import { writeFileSync, existsSync } from "fs";
|
|
5
|
-
import inquirer from "inquirer";
|
|
6
|
-
import { dirname } from "path";
|
|
7
|
-
import { fileURLToPath } from "url";
|
|
8
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
export default function (program) {
|
|
10
|
-
return new Command("init")
|
|
11
|
-
.description("Initialize new schema file")
|
|
12
|
-
.option("--force", "Overwrite existing files")
|
|
13
|
-
.action(async (options, command) => {
|
|
14
|
-
const rootOpts = command.parent.opts();
|
|
15
|
-
const schemaPath = rootOpts.schema;
|
|
16
|
-
if (existsSync(schemaPath)) {
|
|
17
|
-
if (!options.force) {
|
|
18
|
-
const { overwrite } = await inquirer.prompt({
|
|
19
|
-
type: "confirm",
|
|
20
|
-
name: "overwrite",
|
|
21
|
-
message: "Schema file already exists. Overwrite?",
|
|
22
|
-
default: false,
|
|
23
|
-
});
|
|
24
|
-
if (!overwrite)
|
|
25
|
-
process.exit(0);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const spinner = ora("Creating new schema...").start();
|
|
29
|
-
try {
|
|
30
|
-
const template = `import { defineSchema } from 'dotenv-gad';
|
|
31
|
-
|
|
32
|
-
export default defineSchema({
|
|
33
|
-
// Add your environment variables here
|
|
34
|
-
PORT: {
|
|
35
|
-
type: 'number',
|
|
36
|
-
default: 3000,
|
|
37
|
-
docs: 'Port to run the server on'
|
|
38
|
-
},
|
|
39
|
-
NODE_ENV: {
|
|
40
|
-
type: 'string',
|
|
41
|
-
enum: ['development', 'production', 'test'],
|
|
42
|
-
default: 'development'
|
|
43
|
-
},
|
|
44
|
-
DB_URL: {
|
|
45
|
-
type: 'string',
|
|
46
|
-
required: true,
|
|
47
|
-
docs: 'Database connection URL'
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
`;
|
|
51
|
-
writeFileSync(schemaPath, template);
|
|
52
|
-
spinner.succeed(chalk.green(`Created ${schemaPath} successfully!`));
|
|
53
|
-
console.log(chalk.dim("\nEdit this file to define your environment schema"));
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
spinner.fail(chalk.red("Failed to create schema file"));
|
|
57
|
-
console.error(error);
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import { writeFileSync } from "fs";
|
|
5
|
-
import { loadSchema } from "./utils.js";
|
|
6
|
-
export default function (program) {
|
|
7
|
-
return new Command("sync")
|
|
8
|
-
.description("Genearte/update .env.example file")
|
|
9
|
-
.option("--output <file>", "Output file path", ".env.example")
|
|
10
|
-
.action(async (options, command) => {
|
|
11
|
-
const rootOpts = command.parent.opts();
|
|
12
|
-
const spinner = ora("Generating .env.example.......").start();
|
|
13
|
-
try {
|
|
14
|
-
const schema = await loadSchema(rootOpts.schema);
|
|
15
|
-
let exampleContent = "# Auto-generated by dotenv-gad\n\n";
|
|
16
|
-
Object.entries(schema).forEach(([key, rule]) => {
|
|
17
|
-
if (rule.sensitive)
|
|
18
|
-
return;
|
|
19
|
-
exampleContent += `# ${rule.docs || "No description available"}\n`;
|
|
20
|
-
exampleContent += `# Type: ${rule.type}\n`;
|
|
21
|
-
if (rule.default !== undefined) {
|
|
22
|
-
exampleContent += `# Default: ${JSON.stringify(rule.default)}\n`;
|
|
23
|
-
}
|
|
24
|
-
exampleContent += `${key}=${rule.default
|
|
25
|
-
? JSON.stringify(rule.default)
|
|
26
|
-
: ""}\n\n`;
|
|
27
|
-
});
|
|
28
|
-
writeFileSync(options.output, exampleContent.trim());
|
|
29
|
-
spinner.succeed(chalk.green(`Generated ${options.output} successfully!`));
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
spinner.fail(chalk.red(`Failed to generate .env.example`));
|
|
33
|
-
console.error(error);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import { writeFileSync } from "fs";
|
|
5
|
-
import { loadSchema } from "./utils.js";
|
|
6
|
-
export default function (program) {
|
|
7
|
-
return new Command("types")
|
|
8
|
-
.description("Generate Typescript types")
|
|
9
|
-
.option("--output <file>", "Output file path", "env.d.ts")
|
|
10
|
-
.action(async (options, command) => {
|
|
11
|
-
const rootOpts = command.parent.opts();
|
|
12
|
-
const spinner = ora("Generating type definitions.......").start();
|
|
13
|
-
try {
|
|
14
|
-
const schema = await loadSchema(rootOpts.schema);
|
|
15
|
-
let typeContent = "// Auto-generated by dotenv-gad\n\ndeclare namespace NodeJS{\n interface ProcessEnv{\n";
|
|
16
|
-
Object.entries(schema).forEach(([key, rule]) => {
|
|
17
|
-
let type;
|
|
18
|
-
switch (rule.type) {
|
|
19
|
-
case "number":
|
|
20
|
-
type = "number";
|
|
21
|
-
break;
|
|
22
|
-
case "boolean":
|
|
23
|
-
type = "boolean";
|
|
24
|
-
break;
|
|
25
|
-
default:
|
|
26
|
-
type = "string";
|
|
27
|
-
}
|
|
28
|
-
typeContent += ` ${key}${rule.required ? "" : "?"}:${type};\n`;
|
|
29
|
-
});
|
|
30
|
-
typeContent += " }\n}\n";
|
|
31
|
-
writeFileSync(options.output, typeContent);
|
|
32
|
-
spinner.succeed(chalk.green(`Generated ${options.output} successfully!`));
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
spinner.fail(chalk.red("Failed to generate type definitions"));
|
|
36
|
-
console.error(error);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
2
|
-
import { dirname, join } from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
import { transformSync } from "esbuild";
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
export async function loadSchema(schemaPath) {
|
|
7
|
-
try {
|
|
8
|
-
if (schemaPath.endsWith(".ts")) {
|
|
9
|
-
const code = readFileSync(schemaPath, "utf-8");
|
|
10
|
-
const result = transformSync(code, {
|
|
11
|
-
format: "esm",
|
|
12
|
-
loader: "ts",
|
|
13
|
-
target: "esnext",
|
|
14
|
-
});
|
|
15
|
-
// Create temporary file
|
|
16
|
-
const tempFile = join(__dirname, "../../temp-schema.mjs");
|
|
17
|
-
writeFileSync(tempFile, result.code);
|
|
18
|
-
try {
|
|
19
|
-
// Import with query string to bust cache
|
|
20
|
-
const module = await import(`${tempFile}?t=${Date.now()}`);
|
|
21
|
-
return module.default;
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
// Clean up temp file
|
|
25
|
-
unlinkSync(tempFile);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
else if (schemaPath.endsWith(".js")) {
|
|
29
|
-
const module = await import(schemaPath);
|
|
30
|
-
return module.default;
|
|
31
|
-
}
|
|
32
|
-
else if (schemaPath.endsWith(".json")) {
|
|
33
|
-
return JSON.parse(readFileSync(schemaPath, "utf-8"));
|
|
34
|
-
}
|
|
35
|
-
throw new Error("Unsupported schema format. Use .ts, .js or .json");
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
throw new Error(`Failed to load schema: ${error.message}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
package/dist/errors.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export class AggregateError extends Error {
|
|
2
|
-
errors;
|
|
3
|
-
constructor(errors, message) {
|
|
4
|
-
super(message);
|
|
5
|
-
this.errors = errors;
|
|
6
|
-
this.name = "AggregateError";
|
|
7
|
-
Object.setPrototypeOf(this, AggregateError.prototype);
|
|
8
|
-
}
|
|
9
|
-
toString() {
|
|
10
|
-
const errorList = this.errors
|
|
11
|
-
.map((e) => {
|
|
12
|
-
let msg = ` - ${e.key}:${e.message}`;
|
|
13
|
-
if (e.value !== undefined)
|
|
14
|
-
msg += ` (reaceived: ${JSON.stringify(e.value)})`;
|
|
15
|
-
if (e.rule?.docs)
|
|
16
|
-
msg += `\n (${e.rule.docs})`;
|
|
17
|
-
return msg;
|
|
18
|
-
})
|
|
19
|
-
.join("\n");
|
|
20
|
-
return `${this.message}:\n${errorList}`;
|
|
21
|
-
}
|
|
22
|
-
}
|
package/dist/schema.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Define a schema for a set of environment variables.
|
|
3
|
-
*
|
|
4
|
-
* @param schema A record where each key is the name of an environment variable
|
|
5
|
-
* and the value is a `SchemaRule` object that defines the rules for that
|
|
6
|
-
* variable.
|
|
7
|
-
*/
|
|
8
|
-
export function defineSchema(schema) {
|
|
9
|
-
return schema;
|
|
10
|
-
}
|
package/dist/validator.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { AggregateError } from "./errors.js";
|
|
2
|
-
export class EnvValidator {
|
|
3
|
-
schema;
|
|
4
|
-
options;
|
|
5
|
-
errors = [];
|
|
6
|
-
constructor(schema, options) {
|
|
7
|
-
this.schema = schema;
|
|
8
|
-
this.options = options;
|
|
9
|
-
}
|
|
10
|
-
validate(env) {
|
|
11
|
-
this.errors = [];
|
|
12
|
-
const result = {};
|
|
13
|
-
for (const [key, rule] of Object.entries(this.schema)) {
|
|
14
|
-
try {
|
|
15
|
-
result[key] = this.validateKey(key, rule, env[key]);
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
if (error instanceof Error) {
|
|
19
|
-
this.errors.push({
|
|
20
|
-
key,
|
|
21
|
-
message: error.message,
|
|
22
|
-
value: env[key],
|
|
23
|
-
rule,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
if (this.errors.length > 0) {
|
|
29
|
-
const keys = this.errors.map((e) => e.key).join(", ");
|
|
30
|
-
throw new AggregateError(this.errors, `Environment validation failed: ${keys}`);
|
|
31
|
-
}
|
|
32
|
-
if (this.options?.strict) {
|
|
33
|
-
const extraVars = Object.keys(env).filter((k) => !(k in this.schema));
|
|
34
|
-
if (extraVars.length > 0) {
|
|
35
|
-
throw new Error(`Unexpected environment variables: ${extraVars.join(", ")}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
validateKey(key, rule, value) {
|
|
41
|
-
const effectiveRule = this.getEffectiveRule(key, rule);
|
|
42
|
-
if (value === undefined) {
|
|
43
|
-
if (effectiveRule.required)
|
|
44
|
-
throw new Error(`Missing required environment variable`);
|
|
45
|
-
return effectiveRule.default;
|
|
46
|
-
}
|
|
47
|
-
if (effectiveRule.transform) {
|
|
48
|
-
value = effectiveRule.transform(value);
|
|
49
|
-
}
|
|
50
|
-
switch (effectiveRule.type) {
|
|
51
|
-
case "string":
|
|
52
|
-
value = String(value).trim();
|
|
53
|
-
if (effectiveRule.minLength && value.length < effectiveRule.minLength) {
|
|
54
|
-
throw new Error(`Environment variable ${key} must be at least ${effectiveRule.minLength} characters`);
|
|
55
|
-
}
|
|
56
|
-
if (effectiveRule.maxLength && value.length > effectiveRule.maxLength) {
|
|
57
|
-
throw new Error(`Environment variable ${key} must be at most ${effectiveRule.maxLength} characters`);
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
case "number":
|
|
61
|
-
value = Number(value);
|
|
62
|
-
if (isNaN(value)) {
|
|
63
|
-
throw new Error(`Environment variable ${key} must be a number`);
|
|
64
|
-
}
|
|
65
|
-
if (effectiveRule.min !== undefined && value < effectiveRule.min) {
|
|
66
|
-
throw new Error(`Environment variable ${key} must be at least ${effectiveRule.min}`);
|
|
67
|
-
}
|
|
68
|
-
if (effectiveRule.max !== undefined && value > effectiveRule.max) {
|
|
69
|
-
throw new Error(`Environment variable ${key} must be at most ${effectiveRule.max}`);
|
|
70
|
-
}
|
|
71
|
-
break;
|
|
72
|
-
case "boolean":
|
|
73
|
-
if (typeof value === "string") {
|
|
74
|
-
value = value.toLowerCase();
|
|
75
|
-
if (value === "true") {
|
|
76
|
-
value = true;
|
|
77
|
-
}
|
|
78
|
-
else if (value === "false") {
|
|
79
|
-
value = false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (typeof value !== "boolean") {
|
|
83
|
-
throw new Error(`Environment variable ${key} must be a boolean (true/false)`);
|
|
84
|
-
}
|
|
85
|
-
value = Boolean(value);
|
|
86
|
-
break;
|
|
87
|
-
case "url":
|
|
88
|
-
try {
|
|
89
|
-
new URL(String(value));
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
throw new Error("Must be a valid URL");
|
|
93
|
-
}
|
|
94
|
-
break;
|
|
95
|
-
case "email":
|
|
96
|
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value))) {
|
|
97
|
-
throw new Error("Must be a valid email");
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
case "ip":
|
|
101
|
-
if (!/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(String(value))) {
|
|
102
|
-
throw new Error("Must be a valid IP address");
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
case "port":
|
|
106
|
-
const port = Number(value);
|
|
107
|
-
if (isNaN(port))
|
|
108
|
-
throw new Error("Must be a number");
|
|
109
|
-
if (port < 1 || port > 65535) {
|
|
110
|
-
throw new Error("Must be between 1 and 65535");
|
|
111
|
-
}
|
|
112
|
-
break;
|
|
113
|
-
case "json":
|
|
114
|
-
try {
|
|
115
|
-
value = JSON.parse(value);
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
throw new Error("Must be valid JSON");
|
|
119
|
-
}
|
|
120
|
-
break;
|
|
121
|
-
case "array":
|
|
122
|
-
if (!Array.isArray(value)) {
|
|
123
|
-
try {
|
|
124
|
-
value = JSON.parse(value);
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
throw new Error("Must be a valid array or JSON array string");
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (effectiveRule.items) {
|
|
131
|
-
value = value.map((item, i) => {
|
|
132
|
-
try {
|
|
133
|
-
return this.validateKey(`${key}[${i}]`, effectiveRule.items, item);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
throw new Error(`Array item '${i}':${error.message}`);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
break;
|
|
141
|
-
case "object":
|
|
142
|
-
if (typeof value === "string") {
|
|
143
|
-
try {
|
|
144
|
-
value = JSON.parse(value);
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
throw new Error("Must be a valid object or JSON string");
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (effectiveRule.properties) {
|
|
151
|
-
const obj = {};
|
|
152
|
-
for (const [prop, propRule] of Object.entries(effectiveRule.properties)) {
|
|
153
|
-
try {
|
|
154
|
-
obj[prop] = this.validateKey(`${key}.${prop}`, propRule, value[prop]);
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
throw new Error(`Property '${prop}':${error.message}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
value = obj;
|
|
161
|
-
}
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
if (effectiveRule.enum && !effectiveRule.enum.includes(value)) {
|
|
165
|
-
throw new Error(`Environment variable ${key} must be one of ${effectiveRule.enum.join(", ")}`);
|
|
166
|
-
}
|
|
167
|
-
if (effectiveRule.regex && !effectiveRule.regex.test(value)) {
|
|
168
|
-
throw new Error(`Environment variable ${key} must match ${effectiveRule.regex}`);
|
|
169
|
-
}
|
|
170
|
-
if (effectiveRule.validate && !effectiveRule.validate(value)) {
|
|
171
|
-
throw new Error(effectiveRule.error || "Custom validation failed");
|
|
172
|
-
}
|
|
173
|
-
return value;
|
|
174
|
-
}
|
|
175
|
-
getEffectiveRule(key, rule) {
|
|
176
|
-
const envName = process.env.NODE_ENV || "development";
|
|
177
|
-
const envRule = rule.env?.[envName] || {};
|
|
178
|
-
return { ...rule, ...envRule };
|
|
179
|
-
}
|
|
180
|
-
}
|