@karmaniverous/get-dotenv 6.0.0-1 → 6.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/README.md +91 -379
- package/dist/cli.d.ts +569 -0
- package/dist/cli.mjs +18877 -0
- package/dist/cliHost.d.ts +528 -184
- package/dist/cliHost.mjs +1977 -1428
- package/dist/config.d.ts +191 -14
- package/dist/config.mjs +266 -81
- package/dist/env-overlay.d.ts +223 -16
- package/dist/env-overlay.mjs +185 -4
- package/dist/getdotenv.cli.mjs +18025 -3196
- package/dist/index.d.ts +623 -256
- package/dist/index.mjs +18045 -3206
- package/dist/plugins-aws.d.ts +221 -91
- package/dist/plugins-aws.mjs +2411 -369
- package/dist/plugins-batch.d.ts +300 -103
- package/dist/plugins-batch.mjs +2560 -484
- package/dist/plugins-cmd.d.ts +229 -106
- package/dist/plugins-cmd.mjs +2518 -790
- package/dist/plugins-init.d.ts +221 -95
- package/dist/plugins-init.mjs +2170 -105
- package/dist/plugins.d.ts +246 -125
- package/dist/plugins.mjs +17941 -1968
- package/dist/templates/cli/index.ts +25 -0
- package/{templates/cli/ts → dist/templates/cli}/plugins/hello.ts +13 -9
- package/dist/templates/config/js/getdotenv.config.js +20 -0
- package/dist/templates/config/json/local/getdotenv.config.local.json +7 -0
- package/dist/templates/config/json/public/getdotenv.config.json +9 -0
- package/dist/templates/config/public/getdotenv.config.json +8 -0
- package/dist/templates/config/ts/getdotenv.config.ts +28 -0
- package/dist/templates/config/yaml/local/getdotenv.config.local.yaml +7 -0
- package/dist/templates/config/yaml/public/getdotenv.config.yaml +7 -0
- package/dist/templates/getdotenv.config.js +20 -0
- package/dist/templates/getdotenv.config.json +9 -0
- package/dist/templates/getdotenv.config.local.json +7 -0
- package/dist/templates/getdotenv.config.local.yaml +7 -0
- package/dist/templates/getdotenv.config.ts +28 -0
- package/dist/templates/getdotenv.config.yaml +7 -0
- package/dist/templates/hello.ts +42 -0
- package/dist/templates/index.ts +25 -0
- package/dist/templates/js/getdotenv.config.js +20 -0
- package/dist/templates/json/local/getdotenv.config.local.json +7 -0
- package/dist/templates/json/public/getdotenv.config.json +9 -0
- package/dist/templates/local/getdotenv.config.local.json +7 -0
- package/dist/templates/local/getdotenv.config.local.yaml +7 -0
- package/dist/templates/plugins/hello.ts +42 -0
- package/dist/templates/public/getdotenv.config.json +9 -0
- package/dist/templates/public/getdotenv.config.yaml +7 -0
- package/dist/templates/ts/getdotenv.config.ts +28 -0
- package/dist/templates/yaml/local/getdotenv.config.local.yaml +7 -0
- package/dist/templates/yaml/public/getdotenv.config.yaml +7 -0
- package/getdotenv.config.json +1 -19
- package/package.json +42 -39
- package/templates/cli/index.ts +25 -0
- package/templates/cli/plugins/hello.ts +42 -0
- package/templates/config/js/getdotenv.config.js +8 -3
- package/templates/config/json/public/getdotenv.config.json +0 -3
- package/templates/config/public/getdotenv.config.json +0 -5
- package/templates/config/ts/getdotenv.config.ts +8 -3
- package/templates/config/yaml/public/getdotenv.config.yaml +0 -3
- package/dist/plugins-demo.d.ts +0 -204
- package/dist/plugins-demo.mjs +0 -496
- package/templates/cli/ts/index.ts +0 -9
package/dist/config.d.ts
CHANGED
|
@@ -1,36 +1,188 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Minimal root options shape shared by CLI and generator layers.
|
|
3
|
+
* Keep keys optional to respect exactOptionalPropertyTypes semantics.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
interface RootOptionsShape {
|
|
8
|
+
/** Target environment (dotenv-expanded). */
|
|
9
|
+
env?: string;
|
|
10
|
+
/** Explicit variable overrides (dotenv-expanded). */
|
|
11
|
+
vars?: string;
|
|
12
|
+
/** Command to execute (dotenv-expanded). */
|
|
13
|
+
command?: string;
|
|
14
|
+
/** Output path for the consolidated environment file (dotenv-expanded). */
|
|
15
|
+
outputPath?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Shell execution strategy.
|
|
18
|
+
* - `true`: use default OS shell.
|
|
19
|
+
* - `false`: use plain execution (no shell).
|
|
20
|
+
* - string: use specific shell path.
|
|
21
|
+
*/
|
|
22
|
+
shell?: string | boolean;
|
|
23
|
+
/** Whether to load variables into `process.env`. */
|
|
24
|
+
loadProcess?: boolean;
|
|
25
|
+
/** Exclude all variables from loading. */
|
|
26
|
+
excludeAll?: boolean;
|
|
27
|
+
/** Exclude dynamic variables. */
|
|
28
|
+
excludeDynamic?: boolean;
|
|
29
|
+
/** Exclude environment-specific variables. */
|
|
30
|
+
excludeEnv?: boolean;
|
|
31
|
+
/** Exclude global variables. */
|
|
32
|
+
excludeGlobal?: boolean;
|
|
33
|
+
/** Exclude private variables. */
|
|
34
|
+
excludePrivate?: boolean;
|
|
35
|
+
/** Exclude public variables. */
|
|
36
|
+
excludePublic?: boolean;
|
|
37
|
+
/** Enable console logging of loaded variables. */
|
|
38
|
+
log?: boolean;
|
|
39
|
+
/** Enable debug logging to stderr. */
|
|
40
|
+
debug?: boolean;
|
|
41
|
+
/** Capture child process stdio (useful for tests/CI). */
|
|
42
|
+
capture?: boolean;
|
|
43
|
+
/** Fail on validation errors (schema/requiredKeys). */
|
|
44
|
+
strict?: boolean;
|
|
45
|
+
/** Enable presentation-time redaction of secret-like keys. */
|
|
46
|
+
redact?: boolean;
|
|
47
|
+
/** Enable entropy warnings for high-entropy values. */
|
|
48
|
+
warnEntropy?: boolean;
|
|
49
|
+
/** Entropy threshold (bits/char) for warnings (default 3.8). */
|
|
50
|
+
entropyThreshold?: number;
|
|
51
|
+
/** Minimum string length to check for entropy (default 16). */
|
|
52
|
+
entropyMinLength?: number;
|
|
53
|
+
/** Regex patterns for keys to exclude from entropy checks. */
|
|
54
|
+
entropyWhitelist?: ReadonlyArray<string>;
|
|
55
|
+
/** Additional regex patterns for keys to redact. */
|
|
56
|
+
redactPatterns?: string[];
|
|
57
|
+
/** Default target environment when not specified. */
|
|
58
|
+
defaultEnv?: string;
|
|
59
|
+
/** Token indicating a dotenv file (default: ".env"). */
|
|
60
|
+
dotenvToken?: string;
|
|
61
|
+
/** Path to dynamic variables module (default: undefined). */
|
|
62
|
+
dynamicPath?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Emit diagnostics for child env composition.
|
|
65
|
+
* - `true`: trace all keys.
|
|
66
|
+
* - `string[]`: trace selected keys.
|
|
67
|
+
*/
|
|
68
|
+
trace?: boolean | string[];
|
|
69
|
+
/** Paths to search for dotenv files (space-delimited string or array). */
|
|
70
|
+
paths?: string;
|
|
71
|
+
/** Delimiter for paths string (default: space). */
|
|
72
|
+
pathsDelimiter?: string;
|
|
73
|
+
/** Regex pattern for paths delimiter. */
|
|
74
|
+
pathsDelimiterPattern?: string;
|
|
75
|
+
/** Token indicating private variables (default: "local"). */
|
|
76
|
+
privateToken?: string;
|
|
77
|
+
/** Delimiter for vars string (default: space). */
|
|
78
|
+
varsDelimiter?: string;
|
|
79
|
+
/** Regex pattern for vars delimiter. */
|
|
80
|
+
varsDelimiterPattern?: string;
|
|
81
|
+
/** Assignment operator for vars (default: "="). */
|
|
82
|
+
varsAssignor?: string;
|
|
83
|
+
/** Regex pattern for vars assignment operator. */
|
|
84
|
+
varsAssignorPattern?: string;
|
|
85
|
+
/** Table of named scripts for execution. */
|
|
86
|
+
scripts?: ScriptsTable;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Definition for a single script entry.
|
|
3
90
|
*/
|
|
4
|
-
|
|
91
|
+
interface ScriptDef<TShell extends string | boolean = string | boolean> {
|
|
92
|
+
/** The command string to execute. */
|
|
5
93
|
cmd: string;
|
|
94
|
+
/** Shell override for this script. */
|
|
6
95
|
shell?: TShell | undefined;
|
|
7
|
-
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Scripts table shape.
|
|
99
|
+
*/
|
|
100
|
+
type ScriptsTable<TShell extends string | boolean = string | boolean> = Record<string, string | ScriptDef<TShell>>;
|
|
8
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Canonical programmatic options and helpers for get-dotenv.
|
|
104
|
+
*
|
|
105
|
+
* Requirements addressed:
|
|
106
|
+
* - GetDotenvOptions derives from the Zod schema output (single source of truth).
|
|
107
|
+
* - Removed deprecated/compat flags from the public shape (e.g., useConfigLoader).
|
|
108
|
+
* - Provide Vars-aware defineDynamic and a typed config builder defineGetDotenvConfig\<Vars, Env\>().
|
|
109
|
+
* - Preserve existing behavior for defaults resolution and compat converters.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A minimal representation of an environment key/value mapping.
|
|
114
|
+
* Values may be `undefined` to represent "unset".
|
|
115
|
+
*/
|
|
116
|
+
type ProcessEnv = Record<string, string | undefined>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Unify Scripts via the generic ScriptsTable<TShell> so shell types propagate.
|
|
120
|
+
*/
|
|
9
121
|
type Scripts = ScriptsTable;
|
|
10
122
|
|
|
11
123
|
type GetDotenvConfigResolved = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Help-time/runtime root defaults applied by the host (collapsed families; CLI‑like).
|
|
126
|
+
*/
|
|
127
|
+
rootOptionDefaults?: Partial<RootOptionsShape>;
|
|
128
|
+
/**
|
|
129
|
+
* Help-time visibility for root flags; when a key is false the corresponding
|
|
130
|
+
* option(s) are hidden in root help output.
|
|
131
|
+
*/
|
|
132
|
+
rootOptionVisibility?: Partial<Record<keyof RootOptionsShape, boolean>>;
|
|
133
|
+
/**
|
|
134
|
+
* Merged scripts table for resolving commands and shell behavior.
|
|
135
|
+
* Entries may be strings or objects with `cmd` and optional `shell`.
|
|
136
|
+
*/
|
|
18
137
|
scripts?: Scripts;
|
|
138
|
+
/**
|
|
139
|
+
* Keys required to be present in the final composed environment.
|
|
140
|
+
* Validation occurs after overlays and dynamics.
|
|
141
|
+
*/
|
|
19
142
|
requiredKeys?: string[];
|
|
143
|
+
/**
|
|
144
|
+
* Optional validation schema (e.g., Zod). When present and it exposes
|
|
145
|
+
* `safeParse(finalEnv)`, the host executes it once after overlays.
|
|
146
|
+
*/
|
|
20
147
|
schema?: unknown;
|
|
148
|
+
/**
|
|
149
|
+
* Public global variables (string‑only).
|
|
150
|
+
*/
|
|
21
151
|
vars?: Record<string, string>;
|
|
152
|
+
/**
|
|
153
|
+
* Public per‑environment variables (string‑only).
|
|
154
|
+
*/
|
|
22
155
|
envVars?: Record<string, Record<string, string>>;
|
|
156
|
+
/**
|
|
157
|
+
* Dynamic variable definitions (JS/TS configs only).
|
|
158
|
+
*/
|
|
23
159
|
dynamic?: unknown;
|
|
160
|
+
/**
|
|
161
|
+
* Per‑plugin configuration slices keyed by realized mount path
|
|
162
|
+
* (for example, "aws/whoami").
|
|
163
|
+
*/
|
|
24
164
|
plugins?: Record<string, unknown>;
|
|
25
165
|
};
|
|
26
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Privacy scope of a configuration file ('public' is checked into git, 'local' is gitignored).
|
|
169
|
+
*/
|
|
27
170
|
type ConfigPrivacy = 'public' | 'local';
|
|
171
|
+
/**
|
|
172
|
+
* Origin scope of a configuration file ('packaged' inside the library, 'project' in the consumer repo).
|
|
173
|
+
*/
|
|
28
174
|
type ConfigScope = 'packaged' | 'project';
|
|
29
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Represents a discovered configuration file.
|
|
177
|
+
*/
|
|
178
|
+
interface ConfigFile {
|
|
179
|
+
/** Absolute path to the config file. */
|
|
30
180
|
path: string;
|
|
181
|
+
/** Privacy scope (public vs local). */
|
|
31
182
|
privacy: ConfigPrivacy;
|
|
183
|
+
/** Origin scope (packaged vs project). */
|
|
32
184
|
scope: ConfigScope;
|
|
33
|
-
}
|
|
185
|
+
}
|
|
34
186
|
/**
|
|
35
187
|
* Discover JSON/YAML config files in the packaged root and project root.
|
|
36
188
|
* Order: packaged public → project public → project local. */
|
|
@@ -43,18 +195,43 @@ declare const discoverConfigFiles: (importMetaUrl?: string) => Promise<ConfigFil
|
|
|
43
195
|
* For JS/TS: default export is loaded; "dynamic" is allowed.
|
|
44
196
|
*/
|
|
45
197
|
declare const loadConfigFile: (filePath: string) => Promise<GetDotenvConfigResolved>;
|
|
46
|
-
|
|
198
|
+
interface ResolvedConfigSources {
|
|
199
|
+
/** Configuration from the package root (public only). */
|
|
47
200
|
packaged?: GetDotenvConfigResolved;
|
|
201
|
+
/** Configuration from the project root. */
|
|
48
202
|
project?: {
|
|
203
|
+
/** Project public configuration. */
|
|
49
204
|
public?: GetDotenvConfigResolved;
|
|
205
|
+
/** Project local configuration. */
|
|
50
206
|
local?: GetDotenvConfigResolved;
|
|
51
207
|
};
|
|
52
|
-
}
|
|
208
|
+
}
|
|
53
209
|
/**
|
|
54
210
|
* Discover and load configs into resolved shapes, ordered by scope/privacy.
|
|
55
211
|
* JSON/YAML/JS/TS supported; first match per scope/privacy applies.
|
|
56
212
|
*/
|
|
57
213
|
declare const resolveGetDotenvConfigSources: (importMetaUrl?: string) => Promise<ResolvedConfigSources>;
|
|
214
|
+
/**
|
|
215
|
+
* Utility primarily for tests: create a file: URL string from a path.
|
|
216
|
+
* @param p - File path.
|
|
217
|
+
*/
|
|
58
218
|
declare const toFileUrl: (p: string) => string;
|
|
59
219
|
|
|
60
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Validate a composed env against config-provided validation surfaces.
|
|
222
|
+
* Precedence for validation definitions:
|
|
223
|
+
* project.local -\> project.public -\> packaged
|
|
224
|
+
*
|
|
225
|
+
* Behavior:
|
|
226
|
+
* - If a JS/TS `schema` is present, use schema.safeParse(finalEnv).
|
|
227
|
+
* - Else if `requiredKeys` is present, check presence (value !== undefined).
|
|
228
|
+
* - Returns a flat list of issue strings; caller decides warn vs fail.
|
|
229
|
+
*
|
|
230
|
+
* @param finalEnv - Final composed environment to validate.
|
|
231
|
+
* @param sources - Resolved config sources providing `schema` and/or `requiredKeys`.
|
|
232
|
+
* @returns A list of human-readable issue strings (empty when valid).
|
|
233
|
+
*/
|
|
234
|
+
declare const validateEnvAgainstSources: (finalEnv: ProcessEnv, sources: ResolvedConfigSources) => string[];
|
|
235
|
+
|
|
236
|
+
export { discoverConfigFiles, loadConfigFile, resolveGetDotenvConfigSources, toFileUrl, validateEnvAgainstSources };
|
|
237
|
+
export type { ConfigFile, ConfigPrivacy, ConfigScope, ResolvedConfigSources };
|
package/dist/config.mjs
CHANGED
|
@@ -1,30 +1,98 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import { packageDirectory } from 'package-directory';
|
|
3
3
|
import path, { join, extname } from 'path';
|
|
4
|
-
import { pathToFileURL, fileURLToPath } from 'url';
|
|
4
|
+
import url, { pathToFileURL, fileURLToPath } from 'url';
|
|
5
5
|
import YAML from 'yaml';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Zod schemas for programmatic GetDotenv options.
|
|
11
|
+
*
|
|
12
|
+
* Canonical source of truth for options shape. Public types are derived
|
|
13
|
+
* from these schemas (see consumers via z.output\<\>).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Minimal process env representation used by options and helpers.
|
|
17
|
+
* Values may be `undefined` to indicate "unset".
|
|
18
|
+
*/
|
|
19
|
+
const processEnvSchema = z.record(z.string(), z.string().optional());
|
|
20
|
+
// RAW: all fields optional — undefined means "inherit" from lower layers.
|
|
21
|
+
const getDotenvOptionsSchemaRaw = z.object({
|
|
22
|
+
defaultEnv: z.string().optional(),
|
|
23
|
+
dotenvToken: z.string().optional(),
|
|
24
|
+
dynamicPath: z.string().optional(),
|
|
25
|
+
// Dynamic map is intentionally wide for now; refine once sources are normalized.
|
|
26
|
+
dynamic: z.record(z.string(), z.unknown()).optional(),
|
|
27
|
+
env: z.string().optional(),
|
|
28
|
+
excludeDynamic: z.boolean().optional(),
|
|
29
|
+
excludeEnv: z.boolean().optional(),
|
|
30
|
+
excludeGlobal: z.boolean().optional(),
|
|
31
|
+
excludePrivate: z.boolean().optional(),
|
|
32
|
+
excludePublic: z.boolean().optional(),
|
|
33
|
+
loadProcess: z.boolean().optional(),
|
|
34
|
+
log: z.boolean().optional(),
|
|
35
|
+
logger: z.unknown().default(console),
|
|
36
|
+
outputPath: z.string().optional(),
|
|
37
|
+
paths: z.array(z.string()).optional(),
|
|
38
|
+
privateToken: z.string().optional(),
|
|
39
|
+
vars: processEnvSchema.optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Zod schemas for CLI-facing GetDotenv options (raw/resolved stubs).
|
|
44
|
+
*
|
|
45
|
+
* RAW allows stringly inputs (paths/vars + splitters). RESOLVED will later
|
|
46
|
+
* reflect normalized types (paths: string[], vars: ProcessEnv), applied in the
|
|
47
|
+
* CLI resolution pipeline.
|
|
48
|
+
*/
|
|
49
|
+
const getDotenvCliOptionsSchemaRaw = getDotenvOptionsSchemaRaw.extend({
|
|
50
|
+
// CLI-specific fields (stringly inputs before preprocessing)
|
|
51
|
+
debug: z.boolean().optional(),
|
|
52
|
+
strict: z.boolean().optional(),
|
|
53
|
+
capture: z.boolean().optional(),
|
|
54
|
+
trace: z.union([z.boolean(), z.array(z.string())]).optional(),
|
|
55
|
+
redact: z.boolean().optional(),
|
|
56
|
+
warnEntropy: z.boolean().optional(),
|
|
57
|
+
entropyThreshold: z.number().optional(),
|
|
58
|
+
entropyMinLength: z.number().optional(),
|
|
59
|
+
entropyWhitelist: z.array(z.string()).optional(),
|
|
60
|
+
redactPatterns: z.array(z.string()).optional(),
|
|
61
|
+
paths: z.string().optional(),
|
|
62
|
+
pathsDelimiter: z.string().optional(),
|
|
63
|
+
pathsDelimiterPattern: z.string().optional(),
|
|
64
|
+
scripts: z.record(z.string(), z.unknown()).optional(),
|
|
65
|
+
shell: z.union([z.boolean(), z.string()]).optional(),
|
|
66
|
+
vars: z.string().optional(),
|
|
67
|
+
varsAssignor: z.string().optional(),
|
|
68
|
+
varsAssignorPattern: z.string().optional(),
|
|
69
|
+
varsDelimiter: z.string().optional(),
|
|
70
|
+
varsDelimiterPattern: z.string().optional(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const visibilityMap = z.record(z.string(), z.boolean());
|
|
8
74
|
/**
|
|
9
75
|
* Zod schemas for configuration files discovered by the new loader.
|
|
10
76
|
*
|
|
11
77
|
* Notes:
|
|
12
|
-
* - RAW: all fields optional;
|
|
13
|
-
*
|
|
14
|
-
*
|
|
78
|
+
* - RAW: all fields optional; only allowed top-level keys are:
|
|
79
|
+
* - rootOptionDefaults, rootOptionVisibility
|
|
80
|
+
* - scripts, vars, envVars
|
|
81
|
+
* - dynamic (JS/TS only), schema (JS/TS only)
|
|
82
|
+
* - plugins, requiredKeys
|
|
83
|
+
* - RESOLVED: mirrors RAW (no path normalization).
|
|
84
|
+
* - For JSON/YAML configs, the loader rejects "dynamic" and "schema" (JS/TS only).
|
|
15
85
|
*/
|
|
16
86
|
// String-only env value map
|
|
17
87
|
const stringMap = z.record(z.string(), z.string());
|
|
18
88
|
const envStringMap = z.record(z.string(), stringMap);
|
|
19
|
-
|
|
20
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Raw configuration schema for get‑dotenv config files (JSON/YAML/JS/TS).
|
|
91
|
+
* Validates allowed top‑level keys without performing path normalization.
|
|
92
|
+
*/
|
|
21
93
|
const getDotenvConfigSchemaRaw = z.object({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
paths: rawPathsSchema,
|
|
25
|
-
loadProcess: z.boolean().optional(),
|
|
26
|
-
log: z.boolean().optional(),
|
|
27
|
-
shell: z.union([z.string(), z.boolean()]).optional(),
|
|
94
|
+
rootOptionDefaults: getDotenvCliOptionsSchemaRaw.optional(),
|
|
95
|
+
rootOptionVisibility: visibilityMap.optional(),
|
|
28
96
|
scripts: z.record(z.string(), z.unknown()).optional(), // Scripts validation left wide; generator validates elsewhere
|
|
29
97
|
requiredKeys: z.array(z.string()).optional(),
|
|
30
98
|
schema: z.unknown().optional(), // JS/TS-only; loader rejects in JSON/YAML
|
|
@@ -35,109 +103,163 @@ const getDotenvConfigSchemaRaw = z.object({
|
|
|
35
103
|
// Per-plugin config bag; validated by plugins/host when used.
|
|
36
104
|
plugins: z.record(z.string(), z.unknown()).optional(),
|
|
37
105
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Resolved configuration schema which preserves the raw shape while narrowing
|
|
108
|
+
* the output to {@link GetDotenvConfigResolved}. Consumers get a strongly typed
|
|
109
|
+
* object, while the underlying validation remains Zod‑driven.
|
|
110
|
+
*/
|
|
111
|
+
const getDotenvConfigSchemaResolved = getDotenvConfigSchemaRaw.transform((raw) => raw);
|
|
44
112
|
|
|
45
|
-
// Discovery candidates (first match wins per scope/privacy).
|
|
46
|
-
// Order preserves historical JSON/YAML precedence; JS/TS added afterwards.
|
|
47
|
-
const PUBLIC_FILENAMES = [
|
|
48
|
-
'getdotenv.config.json',
|
|
49
|
-
'getdotenv.config.yaml',
|
|
50
|
-
'getdotenv.config.yml',
|
|
51
|
-
'getdotenv.config.js',
|
|
52
|
-
'getdotenv.config.mjs',
|
|
53
|
-
'getdotenv.config.cjs',
|
|
54
|
-
'getdotenv.config.ts',
|
|
55
|
-
'getdotenv.config.mts',
|
|
56
|
-
'getdotenv.config.cts',
|
|
57
|
-
];
|
|
58
|
-
const LOCAL_FILENAMES = [
|
|
59
|
-
'getdotenv.config.local.json',
|
|
60
|
-
'getdotenv.config.local.yaml',
|
|
61
|
-
'getdotenv.config.local.yml',
|
|
62
|
-
'getdotenv.config.local.js',
|
|
63
|
-
'getdotenv.config.local.mjs',
|
|
64
|
-
'getdotenv.config.local.cjs',
|
|
65
|
-
'getdotenv.config.local.ts',
|
|
66
|
-
'getdotenv.config.local.mts',
|
|
67
|
-
'getdotenv.config.local.cts',
|
|
68
|
-
];
|
|
69
|
-
const isYaml = (p) => ['.yaml', '.yml'].includes(extname(p).toLowerCase());
|
|
70
|
-
const isJson = (p) => extname(p).toLowerCase() === '.json';
|
|
71
|
-
const isJsOrTs = (p) => ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(extname(p).toLowerCase());
|
|
72
|
-
// --- Internal JS/TS module loader helpers (default export) ---
|
|
73
113
|
const importDefault = async (fileUrl) => {
|
|
74
114
|
const mod = (await import(fileUrl));
|
|
75
115
|
return mod.default;
|
|
76
116
|
};
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
117
|
+
const cacheHash = (absPath, mtimeMs) => createHash('sha1')
|
|
118
|
+
.update(absPath)
|
|
119
|
+
.update(String(mtimeMs))
|
|
120
|
+
.digest('hex')
|
|
121
|
+
.slice(0, 12);
|
|
122
|
+
/**
|
|
123
|
+
* Remove older compiled cache files for a given source base name, keeping
|
|
124
|
+
* at most `keep` most-recent files. Errors are ignored by design.
|
|
125
|
+
*/
|
|
126
|
+
const cleanupOldCacheFiles = async (cacheDir, baseName, keep = Math.max(1, Number.parseInt(process.env.GETDOTENV_CACHE_KEEP ?? '2'))) => {
|
|
127
|
+
try {
|
|
128
|
+
const entries = await fs.readdir(cacheDir);
|
|
129
|
+
const mine = entries
|
|
130
|
+
.filter((f) => f.startsWith(`${baseName}.`) && f.endsWith('.mjs'))
|
|
131
|
+
.map((f) => path.join(cacheDir, f));
|
|
132
|
+
if (mine.length <= keep)
|
|
133
|
+
return;
|
|
134
|
+
const stats = await Promise.all(mine.map(async (p) => ({ p, mtimeMs: (await fs.stat(p)).mtimeMs })));
|
|
135
|
+
stats.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
136
|
+
const toDelete = stats.slice(keep).map((s) => s.p);
|
|
137
|
+
await Promise.all(toDelete.map(async (p) => {
|
|
138
|
+
try {
|
|
139
|
+
await fs.remove(p);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// best-effort cleanup
|
|
143
|
+
}
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// best-effort cleanup
|
|
148
|
+
}
|
|
85
149
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Load a module default export from a JS/TS file with robust fallbacks.
|
|
152
|
+
*
|
|
153
|
+
* Behavior by extension:
|
|
154
|
+
*
|
|
155
|
+
* - `.js`/`.mjs`/`.cjs`: direct dynamic import.
|
|
156
|
+
* - `.ts`/`.mts`/`.cts`/`.tsx`:
|
|
157
|
+
* - try direct dynamic import (when a TS loader is active),
|
|
158
|
+
* - else compile via `esbuild` to a cached `.mjs` file and import,
|
|
159
|
+
* - else fallback to `typescript.transpileModule` for simple modules.
|
|
160
|
+
*
|
|
161
|
+
* @typeParam T - Type of the expected default export.
|
|
162
|
+
* @param absPath - Absolute path to the source file.
|
|
163
|
+
* @param cacheDirName - Cache subfolder under `.tsbuild/`.
|
|
164
|
+
* @returns A `Promise\<T | undefined\>` resolving to the default export (if any).
|
|
165
|
+
*/
|
|
166
|
+
const loadModuleDefault = async (absPath, cacheDirName) => {
|
|
167
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
168
|
+
const fileUrl = url.pathToFileURL(absPath).toString();
|
|
169
|
+
if (!['.ts', '.mts', '.cts', '.tsx'].includes(ext)) {
|
|
90
170
|
return importDefault(fileUrl);
|
|
91
171
|
}
|
|
92
|
-
// Try direct import first
|
|
172
|
+
// Try direct import first (TS loader active)
|
|
93
173
|
try {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
return
|
|
174
|
+
const dyn = await importDefault(fileUrl);
|
|
175
|
+
if (dyn)
|
|
176
|
+
return dyn;
|
|
97
177
|
}
|
|
98
178
|
catch {
|
|
99
|
-
/*
|
|
179
|
+
/* fall through */
|
|
100
180
|
}
|
|
101
|
-
|
|
181
|
+
const stat = await fs.stat(absPath);
|
|
182
|
+
const hash = cacheHash(absPath, stat.mtimeMs);
|
|
183
|
+
const cacheDir = path.resolve('.tsbuild', cacheDirName);
|
|
184
|
+
await fs.ensureDir(cacheDir);
|
|
185
|
+
const cacheFile = path.join(cacheDir, `${path.basename(absPath)}.${hash}.mjs`);
|
|
186
|
+
// Try esbuild
|
|
102
187
|
try {
|
|
103
188
|
const esbuild = (await import('esbuild'));
|
|
104
|
-
const outDir = await ensureDir(path.resolve('.tsbuild', 'getdotenv-config'));
|
|
105
|
-
const outfile = path.join(outDir, cacheName(absPath, 'bundle'));
|
|
106
189
|
await esbuild.build({
|
|
107
190
|
entryPoints: [absPath],
|
|
108
191
|
bundle: true,
|
|
109
192
|
platform: 'node',
|
|
110
193
|
format: 'esm',
|
|
111
194
|
target: 'node20',
|
|
112
|
-
outfile,
|
|
195
|
+
outfile: cacheFile,
|
|
113
196
|
sourcemap: false,
|
|
114
197
|
logLevel: 'silent',
|
|
115
198
|
});
|
|
116
|
-
|
|
199
|
+
const result = await importDefault(url.pathToFileURL(cacheFile).toString());
|
|
200
|
+
// Best-effort: trim older cache files for this source.
|
|
201
|
+
await cleanupOldCacheFiles(cacheDir, path.basename(absPath));
|
|
202
|
+
return result;
|
|
117
203
|
}
|
|
118
204
|
catch {
|
|
119
|
-
/*
|
|
205
|
+
/* fall through to TS transpile */
|
|
120
206
|
}
|
|
121
|
-
//
|
|
207
|
+
// TypeScript transpile fallback
|
|
122
208
|
try {
|
|
123
209
|
const ts = (await import('typescript'));
|
|
124
|
-
const
|
|
125
|
-
const out = ts.transpileModule(
|
|
210
|
+
const code = await fs.readFile(absPath, 'utf-8');
|
|
211
|
+
const out = ts.transpileModule(code, {
|
|
126
212
|
compilerOptions: {
|
|
127
213
|
module: 'ESNext',
|
|
128
214
|
target: 'ES2022',
|
|
129
215
|
moduleResolution: 'NodeNext',
|
|
130
216
|
},
|
|
131
217
|
}).outputText;
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
218
|
+
await fs.writeFile(cacheFile, out, 'utf-8');
|
|
219
|
+
const result = await importDefault(url.pathToFileURL(cacheFile).toString());
|
|
220
|
+
// Best-effort: trim older cache files for this source.
|
|
221
|
+
await cleanupOldCacheFiles(cacheDir, path.basename(absPath));
|
|
222
|
+
return result;
|
|
136
223
|
}
|
|
137
224
|
catch {
|
|
138
|
-
|
|
225
|
+
// Caller decides final error wording; rethrow for upstream mapping.
|
|
226
|
+
throw new Error(`Unable to load JS/TS module: ${absPath}. Install 'esbuild' or ensure a TS loader.`);
|
|
139
227
|
}
|
|
140
228
|
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @packageDocumentation
|
|
232
|
+
* Configuration discovery and loading for get‑dotenv. Discovers config files
|
|
233
|
+
* in the packaged root and project root, loads JSON/YAML/JS/TS documents, and
|
|
234
|
+
* validates them against Zod schemas.
|
|
235
|
+
*/
|
|
236
|
+
// Discovery candidates (first match wins per scope/privacy).
|
|
237
|
+
// Order preserves historical JSON/YAML precedence; JS/TS added afterwards.
|
|
238
|
+
const PUBLIC_FILENAMES = [
|
|
239
|
+
'getdotenv.config.json',
|
|
240
|
+
'getdotenv.config.yaml',
|
|
241
|
+
'getdotenv.config.yml',
|
|
242
|
+
'getdotenv.config.js',
|
|
243
|
+
'getdotenv.config.mjs',
|
|
244
|
+
'getdotenv.config.cjs',
|
|
245
|
+
'getdotenv.config.ts',
|
|
246
|
+
'getdotenv.config.mts',
|
|
247
|
+
'getdotenv.config.cts',
|
|
248
|
+
];
|
|
249
|
+
const LOCAL_FILENAMES = [
|
|
250
|
+
'getdotenv.config.local.json',
|
|
251
|
+
'getdotenv.config.local.yaml',
|
|
252
|
+
'getdotenv.config.local.yml',
|
|
253
|
+
'getdotenv.config.local.js',
|
|
254
|
+
'getdotenv.config.local.mjs',
|
|
255
|
+
'getdotenv.config.local.cjs',
|
|
256
|
+
'getdotenv.config.local.ts',
|
|
257
|
+
'getdotenv.config.local.mts',
|
|
258
|
+
'getdotenv.config.local.cts',
|
|
259
|
+
];
|
|
260
|
+
const isYaml = (p) => ['.yaml', '.yml'].includes(extname(p).toLowerCase());
|
|
261
|
+
const isJson = (p) => extname(p).toLowerCase() === '.json';
|
|
262
|
+
const isJsOrTs = (p) => ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(extname(p).toLowerCase());
|
|
141
263
|
/**
|
|
142
264
|
* Discover JSON/YAML config files in the packaged root and project root.
|
|
143
265
|
* Order: packaged public → project public → project local. */
|
|
@@ -190,8 +312,8 @@ const loadConfigFile = async (filePath) => {
|
|
|
190
312
|
try {
|
|
191
313
|
const abs = path.resolve(filePath);
|
|
192
314
|
if (isJsOrTs(abs)) {
|
|
193
|
-
// JS/TS support: load default export via robust pipeline.
|
|
194
|
-
const mod = await
|
|
315
|
+
// JS/TS support: load default export via shared robust pipeline.
|
|
316
|
+
const mod = await loadModuleDefault(abs, 'getdotenv-config');
|
|
195
317
|
raw = mod ?? {};
|
|
196
318
|
}
|
|
197
319
|
else {
|
|
@@ -241,7 +363,70 @@ const resolveGetDotenvConfigSources = async (importMetaUrl) => {
|
|
|
241
363
|
}
|
|
242
364
|
return result;
|
|
243
365
|
};
|
|
244
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Utility primarily for tests: create a file: URL string from a path.
|
|
368
|
+
* @param p - File path.
|
|
369
|
+
*/
|
|
245
370
|
const toFileUrl = (p) => pathToFileURL(path.resolve(p)).toString();
|
|
246
371
|
|
|
247
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Validate a composed env against config-provided validation surfaces.
|
|
374
|
+
* Precedence for validation definitions:
|
|
375
|
+
* project.local -\> project.public -\> packaged
|
|
376
|
+
*
|
|
377
|
+
* Behavior:
|
|
378
|
+
* - If a JS/TS `schema` is present, use schema.safeParse(finalEnv).
|
|
379
|
+
* - Else if `requiredKeys` is present, check presence (value !== undefined).
|
|
380
|
+
* - Returns a flat list of issue strings; caller decides warn vs fail.
|
|
381
|
+
*
|
|
382
|
+
* @param finalEnv - Final composed environment to validate.
|
|
383
|
+
* @param sources - Resolved config sources providing `schema` and/or `requiredKeys`.
|
|
384
|
+
* @returns A list of human-readable issue strings (empty when valid).
|
|
385
|
+
*/
|
|
386
|
+
const validateEnvAgainstSources = (finalEnv, sources) => {
|
|
387
|
+
const pick = (getter) => {
|
|
388
|
+
const pl = sources.project?.local;
|
|
389
|
+
const pp = sources.project?.public;
|
|
390
|
+
const pk = sources.packaged;
|
|
391
|
+
return ((pl && getter(pl)) ||
|
|
392
|
+
(pp && getter(pp)) ||
|
|
393
|
+
(pk && getter(pk)) ||
|
|
394
|
+
undefined);
|
|
395
|
+
};
|
|
396
|
+
const schema = pick((cfg) => cfg['schema']);
|
|
397
|
+
if (schema &&
|
|
398
|
+
typeof schema.safeParse === 'function') {
|
|
399
|
+
try {
|
|
400
|
+
const parsed = schema.safeParse(finalEnv);
|
|
401
|
+
if (!parsed.success) {
|
|
402
|
+
// Try to render zod-style issues when available.
|
|
403
|
+
const err = parsed.error;
|
|
404
|
+
const issues = Array.isArray(err.issues) && err.issues.length > 0
|
|
405
|
+
? err.issues.map((i) => {
|
|
406
|
+
const path = Array.isArray(i.path) ? i.path.join('.') : '';
|
|
407
|
+
const msg = i.message ?? 'Invalid value';
|
|
408
|
+
return path ? `[schema] ${path}: ${msg}` : `[schema] ${msg}`;
|
|
409
|
+
})
|
|
410
|
+
: ['[schema] validation failed'];
|
|
411
|
+
return issues;
|
|
412
|
+
}
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// If schema invocation fails, surface a single diagnostic.
|
|
417
|
+
return [
|
|
418
|
+
'[schema] validation failed (unable to execute schema.safeParse)',
|
|
419
|
+
];
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const requiredKeys = pick((cfg) => cfg['requiredKeys']);
|
|
423
|
+
if (Array.isArray(requiredKeys) && requiredKeys.length > 0) {
|
|
424
|
+
const missing = requiredKeys.filter((k) => finalEnv[k] === undefined);
|
|
425
|
+
if (missing.length > 0) {
|
|
426
|
+
return missing.map((k) => `[requiredKeys] missing: ${k}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return [];
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
export { discoverConfigFiles, loadConfigFile, resolveGetDotenvConfigSources, toFileUrl, validateEnvAgainstSources };
|