ezcfg 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +53 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +135 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +43 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
interface ConfigSpec<T> {
|
|
2
|
+
readonly _type: string;
|
|
3
|
+
resolve(errors: string[]): T | undefined;
|
|
4
|
+
}
|
|
5
|
+
type InferSpecType<S> = S extends ConfigSpec<infer T> ? T : S;
|
|
6
|
+
type InferConfigType<S extends Record<string, unknown>> = {
|
|
7
|
+
readonly [K in keyof S]: InferSpecType<S[K]>;
|
|
8
|
+
};
|
|
9
|
+
declare function isConfigSpec(value: unknown): value is ConfigSpec<unknown>;
|
|
10
|
+
|
|
11
|
+
declare class ConfigValidationError extends Error {
|
|
12
|
+
readonly errors: string[];
|
|
13
|
+
constructor(errors: string[]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ConfigOptions {
|
|
17
|
+
loadEnv?: boolean;
|
|
18
|
+
envLoader?: () => void;
|
|
19
|
+
}
|
|
20
|
+
declare function defineConfig<S extends Record<string, unknown>>(schema: S, options?: ConfigOptions): () => InferConfigType<S>;
|
|
21
|
+
|
|
22
|
+
declare function loadEnvFiles({ basePath, nodeEnv, }?: {
|
|
23
|
+
basePath?: string;
|
|
24
|
+
nodeEnv?: string;
|
|
25
|
+
}): void;
|
|
26
|
+
|
|
27
|
+
declare class EnvSpec<T> implements ConfigSpec<T> {
|
|
28
|
+
private readonly envKey;
|
|
29
|
+
private readonly required;
|
|
30
|
+
private readonly defaultValue?;
|
|
31
|
+
private readonly transform?;
|
|
32
|
+
readonly _type = "env";
|
|
33
|
+
constructor(envKey: string, required: boolean, defaultValue?: T | undefined, transform?: ((value: string) => T) | undefined);
|
|
34
|
+
resolve(errors: string[]): T | undefined;
|
|
35
|
+
}
|
|
36
|
+
declare function env(key: string): EnvSpec<string>;
|
|
37
|
+
declare function envOptional(key: string, defaultValue?: string): EnvSpec<string | undefined>;
|
|
38
|
+
declare function envNumber(key: string): EnvSpec<number>;
|
|
39
|
+
declare function envNumberOptional(key: string, defaultValue?: number): EnvSpec<number | undefined>;
|
|
40
|
+
declare function envBoolean(key: string, defaultValue?: boolean): EnvSpec<boolean>;
|
|
41
|
+
declare function envJson<T>(key: string): EnvSpec<T>;
|
|
42
|
+
declare function envJsonOptional<T>(key: string, defaultValue?: T): EnvSpec<T | undefined>;
|
|
43
|
+
|
|
44
|
+
interface DatabaseConfig {
|
|
45
|
+
readonly host: string;
|
|
46
|
+
readonly port: number;
|
|
47
|
+
readonly database: string;
|
|
48
|
+
readonly user: string;
|
|
49
|
+
readonly password?: string;
|
|
50
|
+
toString(): string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { type ConfigOptions, type ConfigSpec, ConfigValidationError, type DatabaseConfig, EnvSpec, type InferConfigType, type InferSpecType, defineConfig, env, envBoolean, envJson, envJsonOptional, envNumber, envNumberOptional, envOptional, isConfigSpec, loadEnvFiles };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
interface ConfigSpec<T> {
|
|
2
|
+
readonly _type: string;
|
|
3
|
+
resolve(errors: string[]): T | undefined;
|
|
4
|
+
}
|
|
5
|
+
type InferSpecType<S> = S extends ConfigSpec<infer T> ? T : S;
|
|
6
|
+
type InferConfigType<S extends Record<string, unknown>> = {
|
|
7
|
+
readonly [K in keyof S]: InferSpecType<S[K]>;
|
|
8
|
+
};
|
|
9
|
+
declare function isConfigSpec(value: unknown): value is ConfigSpec<unknown>;
|
|
10
|
+
|
|
11
|
+
declare class ConfigValidationError extends Error {
|
|
12
|
+
readonly errors: string[];
|
|
13
|
+
constructor(errors: string[]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ConfigOptions {
|
|
17
|
+
loadEnv?: boolean;
|
|
18
|
+
envLoader?: () => void;
|
|
19
|
+
}
|
|
20
|
+
declare function defineConfig<S extends Record<string, unknown>>(schema: S, options?: ConfigOptions): () => InferConfigType<S>;
|
|
21
|
+
|
|
22
|
+
declare function loadEnvFiles({ basePath, nodeEnv, }?: {
|
|
23
|
+
basePath?: string;
|
|
24
|
+
nodeEnv?: string;
|
|
25
|
+
}): void;
|
|
26
|
+
|
|
27
|
+
declare class EnvSpec<T> implements ConfigSpec<T> {
|
|
28
|
+
private readonly envKey;
|
|
29
|
+
private readonly required;
|
|
30
|
+
private readonly defaultValue?;
|
|
31
|
+
private readonly transform?;
|
|
32
|
+
readonly _type = "env";
|
|
33
|
+
constructor(envKey: string, required: boolean, defaultValue?: T | undefined, transform?: ((value: string) => T) | undefined);
|
|
34
|
+
resolve(errors: string[]): T | undefined;
|
|
35
|
+
}
|
|
36
|
+
declare function env(key: string): EnvSpec<string>;
|
|
37
|
+
declare function envOptional(key: string, defaultValue?: string): EnvSpec<string | undefined>;
|
|
38
|
+
declare function envNumber(key: string): EnvSpec<number>;
|
|
39
|
+
declare function envNumberOptional(key: string, defaultValue?: number): EnvSpec<number | undefined>;
|
|
40
|
+
declare function envBoolean(key: string, defaultValue?: boolean): EnvSpec<boolean>;
|
|
41
|
+
declare function envJson<T>(key: string): EnvSpec<T>;
|
|
42
|
+
declare function envJsonOptional<T>(key: string, defaultValue?: T): EnvSpec<T | undefined>;
|
|
43
|
+
|
|
44
|
+
interface DatabaseConfig {
|
|
45
|
+
readonly host: string;
|
|
46
|
+
readonly port: number;
|
|
47
|
+
readonly database: string;
|
|
48
|
+
readonly user: string;
|
|
49
|
+
readonly password?: string;
|
|
50
|
+
toString(): string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { type ConfigOptions, type ConfigSpec, ConfigValidationError, type DatabaseConfig, EnvSpec, type InferConfigType, type InferSpecType, defineConfig, env, envBoolean, envJson, envJsonOptional, envNumber, envNumberOptional, envOptional, isConfigSpec, loadEnvFiles };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ConfigValidationError: () => ConfigValidationError,
|
|
34
|
+
EnvSpec: () => EnvSpec,
|
|
35
|
+
defineConfig: () => defineConfig,
|
|
36
|
+
env: () => env,
|
|
37
|
+
envBoolean: () => envBoolean,
|
|
38
|
+
envJson: () => envJson,
|
|
39
|
+
envJsonOptional: () => envJsonOptional,
|
|
40
|
+
envNumber: () => envNumber,
|
|
41
|
+
envNumberOptional: () => envNumberOptional,
|
|
42
|
+
envOptional: () => envOptional,
|
|
43
|
+
isConfigSpec: () => isConfigSpec,
|
|
44
|
+
loadEnvFiles: () => loadEnvFiles
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(index_exports);
|
|
47
|
+
|
|
48
|
+
// src/config-spec.ts
|
|
49
|
+
function isConfigSpec(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && "resolve" in value && typeof value.resolve === "function";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/errors.ts
|
|
54
|
+
var ConfigValidationError = class extends Error {
|
|
55
|
+
constructor(errors) {
|
|
56
|
+
super(`Config validation failed:
|
|
57
|
+
- ${errors.join("\n - ")}`);
|
|
58
|
+
this.errors = errors;
|
|
59
|
+
this.name = "ConfigValidationError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/load-env-files.ts
|
|
64
|
+
var import_node_fs = __toESM(require("fs"));
|
|
65
|
+
var import_node_path = __toESM(require("path"));
|
|
66
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
67
|
+
function loadEnvFiles({
|
|
68
|
+
basePath,
|
|
69
|
+
nodeEnv
|
|
70
|
+
} = {}) {
|
|
71
|
+
const basePath_ = basePath ?? process.cwd();
|
|
72
|
+
const nodeEnv_ = nodeEnv ?? process.env.NODE_ENV ?? "development";
|
|
73
|
+
const envFiles = [
|
|
74
|
+
".env",
|
|
75
|
+
".env.local",
|
|
76
|
+
`.env.${nodeEnv_}`,
|
|
77
|
+
`.env.${nodeEnv_}.local`
|
|
78
|
+
];
|
|
79
|
+
const loadEnvFile = (filePath) => {
|
|
80
|
+
if (import_node_fs.default.existsSync(filePath)) {
|
|
81
|
+
import_dotenv.default.config({ path: filePath });
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
envFiles.reverse().forEach((file) => {
|
|
85
|
+
const filePath = import_node_path.default.resolve(basePath_, file);
|
|
86
|
+
loadEnvFile(filePath);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/define-config.ts
|
|
91
|
+
function defineConfig(schema, options) {
|
|
92
|
+
let instance = null;
|
|
93
|
+
return () => {
|
|
94
|
+
if (instance && process.env.NODE_ENV !== "test") {
|
|
95
|
+
return instance;
|
|
96
|
+
}
|
|
97
|
+
if (options?.loadEnv) {
|
|
98
|
+
if (options.envLoader) {
|
|
99
|
+
options.envLoader();
|
|
100
|
+
} else {
|
|
101
|
+
loadEnvFiles();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const config = {};
|
|
105
|
+
const errors = [];
|
|
106
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
107
|
+
if (isConfigSpec(value)) {
|
|
108
|
+
config[key] = value.resolve(errors);
|
|
109
|
+
} else {
|
|
110
|
+
config[key] = value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (errors.length > 0) {
|
|
114
|
+
throw new ConfigValidationError(errors);
|
|
115
|
+
}
|
|
116
|
+
instance = config;
|
|
117
|
+
return instance;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/env-spec.ts
|
|
122
|
+
var EnvSpec = class {
|
|
123
|
+
constructor(envKey, required, defaultValue, transform) {
|
|
124
|
+
this.envKey = envKey;
|
|
125
|
+
this.required = required;
|
|
126
|
+
this.defaultValue = defaultValue;
|
|
127
|
+
this.transform = transform;
|
|
128
|
+
}
|
|
129
|
+
_type = "env";
|
|
130
|
+
resolve(errors) {
|
|
131
|
+
const rawValue = process.env[this.envKey];
|
|
132
|
+
if (!rawValue && this.required) {
|
|
133
|
+
errors.push(`Missing required env: ${this.envKey}`);
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
if (rawValue !== void 0) {
|
|
137
|
+
try {
|
|
138
|
+
return this.transform ? this.transform(rawValue) : rawValue;
|
|
139
|
+
} catch (e) {
|
|
140
|
+
errors.push(`Failed to transform ${this.envKey}: ${e.message}`);
|
|
141
|
+
return void 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return this.defaultValue;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
function env(key) {
|
|
148
|
+
return new EnvSpec(key, true);
|
|
149
|
+
}
|
|
150
|
+
function envOptional(key, defaultValue) {
|
|
151
|
+
return new EnvSpec(key, false, defaultValue);
|
|
152
|
+
}
|
|
153
|
+
function envNumber(key) {
|
|
154
|
+
return new EnvSpec(key, true, void 0, Number);
|
|
155
|
+
}
|
|
156
|
+
function envNumberOptional(key, defaultValue) {
|
|
157
|
+
return new EnvSpec(key, false, defaultValue, (v) => v ? Number(v) : void 0);
|
|
158
|
+
}
|
|
159
|
+
function envBoolean(key, defaultValue = false) {
|
|
160
|
+
return new EnvSpec(key, false, defaultValue, (v) => v === "true" || v === "1");
|
|
161
|
+
}
|
|
162
|
+
function envJson(key) {
|
|
163
|
+
return new EnvSpec(key, true, void 0, JSON.parse);
|
|
164
|
+
}
|
|
165
|
+
function envJsonOptional(key, defaultValue) {
|
|
166
|
+
return new EnvSpec(key, false, defaultValue, (v) => v ? JSON.parse(v) : void 0);
|
|
167
|
+
}
|
|
168
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
169
|
+
0 && (module.exports = {
|
|
170
|
+
ConfigValidationError,
|
|
171
|
+
EnvSpec,
|
|
172
|
+
defineConfig,
|
|
173
|
+
env,
|
|
174
|
+
envBoolean,
|
|
175
|
+
envJson,
|
|
176
|
+
envJsonOptional,
|
|
177
|
+
envNumber,
|
|
178
|
+
envNumberOptional,
|
|
179
|
+
envOptional,
|
|
180
|
+
isConfigSpec,
|
|
181
|
+
loadEnvFiles
|
|
182
|
+
});
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config-spec.ts","../src/errors.ts","../src/load-env-files.ts","../src/define-config.ts","../src/env-spec.ts"],"sourcesContent":["export {\n type ConfigSpec,\n type InferSpecType,\n type InferConfigType,\n isConfigSpec,\n} from \"./config-spec\";\n\nexport { ConfigValidationError } from \"./errors\";\n\nexport { defineConfig, type ConfigOptions } from \"./define-config\";\nexport { loadEnvFiles } from \"./load-env-files\";\n\nexport {\n EnvSpec,\n env,\n envOptional,\n envNumber,\n envNumberOptional,\n envBoolean,\n envJson,\n envJsonOptional,\n} from \"./env-spec\";\n\nexport type { DatabaseConfig } from \"./database-config\";\n","export interface ConfigSpec<T> {\n readonly _type: string;\n resolve(errors: string[]): T | undefined;\n}\n\nexport type InferSpecType<S> = S extends ConfigSpec<infer T> ? T : S;\n\nexport type InferConfigType<S extends Record<string, unknown>> = {\n readonly [K in keyof S]: InferSpecType<S[K]>;\n};\n\nexport function isConfigSpec(value: unknown): value is ConfigSpec<unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"resolve\" in value &&\n typeof (value as ConfigSpec<unknown>).resolve === \"function\"\n );\n}\n","export class ConfigValidationError extends Error {\n constructor(public readonly errors: string[]) {\n super(`Config validation failed:\\n - ${errors.join(\"\\n - \")}`);\n this.name = \"ConfigValidationError\";\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport dotenv from \"dotenv\";\n\nexport function loadEnvFiles({\n basePath,\n nodeEnv,\n}: {\n basePath?: string;\n nodeEnv?: string;\n} = {}): void {\n const basePath_ = basePath ?? process.cwd();\n const nodeEnv_ = nodeEnv ?? process.env.NODE_ENV ?? \"development\";\n\n const envFiles = [\n \".env\",\n \".env.local\",\n `.env.${nodeEnv_}`,\n `.env.${nodeEnv_}.local`,\n ];\n\n const loadEnvFile = (filePath: string) => {\n if (fs.existsSync(filePath)) {\n dotenv.config({ path: filePath });\n }\n };\n\n envFiles.reverse().forEach((file) => {\n const filePath = path.resolve(basePath_, file);\n loadEnvFile(filePath);\n });\n}\n","import { type InferConfigType, isConfigSpec } from \"./config-spec\";\nimport { ConfigValidationError } from \"./errors\";\nimport { loadEnvFiles } from \"./load-env-files\";\n\nexport interface ConfigOptions {\n loadEnv?: boolean;\n envLoader?: () => void;\n}\n\nexport function defineConfig<S extends Record<string, unknown>>(\n schema: S,\n options?: ConfigOptions\n): () => InferConfigType<S> {\n let instance: InferConfigType<S> | null = null;\n\n return () => {\n if (instance && process.env.NODE_ENV !== \"test\") {\n return instance;\n }\n\n if (options?.loadEnv) {\n if (options.envLoader) {\n options.envLoader();\n } else {\n loadEnvFiles();\n }\n }\n\n const config = {} as Record<string, unknown>;\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(schema)) {\n if (isConfigSpec(value)) {\n config[key] = value.resolve(errors);\n } else {\n config[key] = value;\n }\n }\n\n if (errors.length > 0) {\n throw new ConfigValidationError(errors);\n }\n\n instance = config as InferConfigType<S>;\n return instance;\n };\n}\n","import type { ConfigSpec } from \"./config-spec\";\n\nexport class EnvSpec<T> implements ConfigSpec<T> {\n readonly _type = \"env\";\n\n constructor(\n private readonly envKey: string,\n private readonly required: boolean,\n private readonly defaultValue?: T,\n private readonly transform?: (value: string) => T\n ) {}\n\n resolve(errors: string[]): T | undefined {\n const rawValue = process.env[this.envKey];\n\n if (!rawValue && this.required) {\n errors.push(`Missing required env: ${this.envKey}`);\n return undefined;\n }\n\n if (rawValue !== undefined) {\n try {\n return this.transform ? this.transform(rawValue) : (rawValue as T);\n } catch (e) {\n errors.push(`Failed to transform ${this.envKey}: ${(e as Error).message}`);\n return undefined;\n }\n }\n\n return this.defaultValue;\n }\n}\n\nexport function env(key: string): EnvSpec<string> {\n return new EnvSpec(key, true);\n}\n\nexport function envOptional(key: string, defaultValue?: string): EnvSpec<string | undefined> {\n return new EnvSpec(key, false, defaultValue);\n}\n\nexport function envNumber(key: string): EnvSpec<number> {\n return new EnvSpec(key, true, undefined, Number);\n}\n\nexport function envNumberOptional(key: string, defaultValue?: number): EnvSpec<number | undefined> {\n return new EnvSpec(key, false, defaultValue, (v) => (v ? Number(v) : undefined));\n}\n\nexport function envBoolean(key: string, defaultValue = false): EnvSpec<boolean> {\n return new EnvSpec(key, false, defaultValue, (v) => v === \"true\" || v === \"1\");\n}\n\nexport function envJson<T>(key: string): EnvSpec<T> {\n return new EnvSpec(key, true, undefined, JSON.parse);\n}\n\nexport function envJsonOptional<T>(key: string, defaultValue?: T): EnvSpec<T | undefined> {\n return new EnvSpec(key, false, defaultValue, (v) => (v ? JSON.parse(v) : undefined));\n}\n"],"mappings":"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","names":["fs","dotenv","path"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/config-spec.ts
|
|
2
|
+
function isConfigSpec(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && "resolve" in value && typeof value.resolve === "function";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/errors.ts
|
|
7
|
+
var ConfigValidationError = class extends Error {
|
|
8
|
+
constructor(errors) {
|
|
9
|
+
super(`Config validation failed:
|
|
10
|
+
- ${errors.join("\n - ")}`);
|
|
11
|
+
this.errors = errors;
|
|
12
|
+
this.name = "ConfigValidationError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/load-env-files.ts
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import dotenv from "dotenv";
|
|
20
|
+
function loadEnvFiles({
|
|
21
|
+
basePath,
|
|
22
|
+
nodeEnv
|
|
23
|
+
} = {}) {
|
|
24
|
+
const basePath_ = basePath ?? process.cwd();
|
|
25
|
+
const nodeEnv_ = nodeEnv ?? process.env.NODE_ENV ?? "development";
|
|
26
|
+
const envFiles = [
|
|
27
|
+
".env",
|
|
28
|
+
".env.local",
|
|
29
|
+
`.env.${nodeEnv_}`,
|
|
30
|
+
`.env.${nodeEnv_}.local`
|
|
31
|
+
];
|
|
32
|
+
const loadEnvFile = (filePath) => {
|
|
33
|
+
if (fs.existsSync(filePath)) {
|
|
34
|
+
dotenv.config({ path: filePath });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
envFiles.reverse().forEach((file) => {
|
|
38
|
+
const filePath = path.resolve(basePath_, file);
|
|
39
|
+
loadEnvFile(filePath);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/define-config.ts
|
|
44
|
+
function defineConfig(schema, options) {
|
|
45
|
+
let instance = null;
|
|
46
|
+
return () => {
|
|
47
|
+
if (instance && process.env.NODE_ENV !== "test") {
|
|
48
|
+
return instance;
|
|
49
|
+
}
|
|
50
|
+
if (options?.loadEnv) {
|
|
51
|
+
if (options.envLoader) {
|
|
52
|
+
options.envLoader();
|
|
53
|
+
} else {
|
|
54
|
+
loadEnvFiles();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const config = {};
|
|
58
|
+
const errors = [];
|
|
59
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
60
|
+
if (isConfigSpec(value)) {
|
|
61
|
+
config[key] = value.resolve(errors);
|
|
62
|
+
} else {
|
|
63
|
+
config[key] = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (errors.length > 0) {
|
|
67
|
+
throw new ConfigValidationError(errors);
|
|
68
|
+
}
|
|
69
|
+
instance = config;
|
|
70
|
+
return instance;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/env-spec.ts
|
|
75
|
+
var EnvSpec = class {
|
|
76
|
+
constructor(envKey, required, defaultValue, transform) {
|
|
77
|
+
this.envKey = envKey;
|
|
78
|
+
this.required = required;
|
|
79
|
+
this.defaultValue = defaultValue;
|
|
80
|
+
this.transform = transform;
|
|
81
|
+
}
|
|
82
|
+
_type = "env";
|
|
83
|
+
resolve(errors) {
|
|
84
|
+
const rawValue = process.env[this.envKey];
|
|
85
|
+
if (!rawValue && this.required) {
|
|
86
|
+
errors.push(`Missing required env: ${this.envKey}`);
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
if (rawValue !== void 0) {
|
|
90
|
+
try {
|
|
91
|
+
return this.transform ? this.transform(rawValue) : rawValue;
|
|
92
|
+
} catch (e) {
|
|
93
|
+
errors.push(`Failed to transform ${this.envKey}: ${e.message}`);
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return this.defaultValue;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
function env(key) {
|
|
101
|
+
return new EnvSpec(key, true);
|
|
102
|
+
}
|
|
103
|
+
function envOptional(key, defaultValue) {
|
|
104
|
+
return new EnvSpec(key, false, defaultValue);
|
|
105
|
+
}
|
|
106
|
+
function envNumber(key) {
|
|
107
|
+
return new EnvSpec(key, true, void 0, Number);
|
|
108
|
+
}
|
|
109
|
+
function envNumberOptional(key, defaultValue) {
|
|
110
|
+
return new EnvSpec(key, false, defaultValue, (v) => v ? Number(v) : void 0);
|
|
111
|
+
}
|
|
112
|
+
function envBoolean(key, defaultValue = false) {
|
|
113
|
+
return new EnvSpec(key, false, defaultValue, (v) => v === "true" || v === "1");
|
|
114
|
+
}
|
|
115
|
+
function envJson(key) {
|
|
116
|
+
return new EnvSpec(key, true, void 0, JSON.parse);
|
|
117
|
+
}
|
|
118
|
+
function envJsonOptional(key, defaultValue) {
|
|
119
|
+
return new EnvSpec(key, false, defaultValue, (v) => v ? JSON.parse(v) : void 0);
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
ConfigValidationError,
|
|
123
|
+
EnvSpec,
|
|
124
|
+
defineConfig,
|
|
125
|
+
env,
|
|
126
|
+
envBoolean,
|
|
127
|
+
envJson,
|
|
128
|
+
envJsonOptional,
|
|
129
|
+
envNumber,
|
|
130
|
+
envNumberOptional,
|
|
131
|
+
envOptional,
|
|
132
|
+
isConfigSpec,
|
|
133
|
+
loadEnvFiles
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config-spec.ts","../src/errors.ts","../src/load-env-files.ts","../src/define-config.ts","../src/env-spec.ts"],"sourcesContent":["export interface ConfigSpec<T> {\n readonly _type: string;\n resolve(errors: string[]): T | undefined;\n}\n\nexport type InferSpecType<S> = S extends ConfigSpec<infer T> ? T : S;\n\nexport type InferConfigType<S extends Record<string, unknown>> = {\n readonly [K in keyof S]: InferSpecType<S[K]>;\n};\n\nexport function isConfigSpec(value: unknown): value is ConfigSpec<unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"resolve\" in value &&\n typeof (value as ConfigSpec<unknown>).resolve === \"function\"\n );\n}\n","export class ConfigValidationError extends Error {\n constructor(public readonly errors: string[]) {\n super(`Config validation failed:\\n - ${errors.join(\"\\n - \")}`);\n this.name = \"ConfigValidationError\";\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport dotenv from \"dotenv\";\n\nexport function loadEnvFiles({\n basePath,\n nodeEnv,\n}: {\n basePath?: string;\n nodeEnv?: string;\n} = {}): void {\n const basePath_ = basePath ?? process.cwd();\n const nodeEnv_ = nodeEnv ?? process.env.NODE_ENV ?? \"development\";\n\n const envFiles = [\n \".env\",\n \".env.local\",\n `.env.${nodeEnv_}`,\n `.env.${nodeEnv_}.local`,\n ];\n\n const loadEnvFile = (filePath: string) => {\n if (fs.existsSync(filePath)) {\n dotenv.config({ path: filePath });\n }\n };\n\n envFiles.reverse().forEach((file) => {\n const filePath = path.resolve(basePath_, file);\n loadEnvFile(filePath);\n });\n}\n","import { type InferConfigType, isConfigSpec } from \"./config-spec\";\nimport { ConfigValidationError } from \"./errors\";\nimport { loadEnvFiles } from \"./load-env-files\";\n\nexport interface ConfigOptions {\n loadEnv?: boolean;\n envLoader?: () => void;\n}\n\nexport function defineConfig<S extends Record<string, unknown>>(\n schema: S,\n options?: ConfigOptions\n): () => InferConfigType<S> {\n let instance: InferConfigType<S> | null = null;\n\n return () => {\n if (instance && process.env.NODE_ENV !== \"test\") {\n return instance;\n }\n\n if (options?.loadEnv) {\n if (options.envLoader) {\n options.envLoader();\n } else {\n loadEnvFiles();\n }\n }\n\n const config = {} as Record<string, unknown>;\n const errors: string[] = [];\n\n for (const [key, value] of Object.entries(schema)) {\n if (isConfigSpec(value)) {\n config[key] = value.resolve(errors);\n } else {\n config[key] = value;\n }\n }\n\n if (errors.length > 0) {\n throw new ConfigValidationError(errors);\n }\n\n instance = config as InferConfigType<S>;\n return instance;\n };\n}\n","import type { ConfigSpec } from \"./config-spec\";\n\nexport class EnvSpec<T> implements ConfigSpec<T> {\n readonly _type = \"env\";\n\n constructor(\n private readonly envKey: string,\n private readonly required: boolean,\n private readonly defaultValue?: T,\n private readonly transform?: (value: string) => T\n ) {}\n\n resolve(errors: string[]): T | undefined {\n const rawValue = process.env[this.envKey];\n\n if (!rawValue && this.required) {\n errors.push(`Missing required env: ${this.envKey}`);\n return undefined;\n }\n\n if (rawValue !== undefined) {\n try {\n return this.transform ? this.transform(rawValue) : (rawValue as T);\n } catch (e) {\n errors.push(`Failed to transform ${this.envKey}: ${(e as Error).message}`);\n return undefined;\n }\n }\n\n return this.defaultValue;\n }\n}\n\nexport function env(key: string): EnvSpec<string> {\n return new EnvSpec(key, true);\n}\n\nexport function envOptional(key: string, defaultValue?: string): EnvSpec<string | undefined> {\n return new EnvSpec(key, false, defaultValue);\n}\n\nexport function envNumber(key: string): EnvSpec<number> {\n return new EnvSpec(key, true, undefined, Number);\n}\n\nexport function envNumberOptional(key: string, defaultValue?: number): EnvSpec<number | undefined> {\n return new EnvSpec(key, false, defaultValue, (v) => (v ? Number(v) : undefined));\n}\n\nexport function envBoolean(key: string, defaultValue = false): EnvSpec<boolean> {\n return new EnvSpec(key, false, defaultValue, (v) => v === \"true\" || v === \"1\");\n}\n\nexport function envJson<T>(key: string): EnvSpec<T> {\n return new EnvSpec(key, true, undefined, JSON.parse);\n}\n\nexport function envJsonOptional<T>(key: string, defaultValue?: T): EnvSpec<T | undefined> {\n return new EnvSpec(key, false, defaultValue, (v) => (v ? JSON.parse(v) : undefined));\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ezcfg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lite and easy configuration management for Node.js",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Sangwoo Hyun <wkdny.hyun@gmail.com>",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"dotenv": "^17.2.3"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^25.1.0",
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"typescript": "^5.9.3",
|
|
33
|
+
"vitest": "^4.0.18"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"config",
|
|
37
|
+
"configuration",
|
|
38
|
+
"env",
|
|
39
|
+
"environment",
|
|
40
|
+
"typescript",
|
|
41
|
+
"type-safe"
|
|
42
|
+
]
|
|
43
|
+
}
|