@karmaniverous/get-dotenv 6.0.0-0 → 6.0.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 +86 -334
- package/dist/cli.d.ts +569 -0
- package/dist/cli.mjs +18788 -0
- package/dist/cliHost.d.ts +548 -253
- package/dist/cliHost.mjs +1990 -1458
- package/dist/config.d.ts +192 -14
- package/dist/config.mjs +256 -81
- package/dist/env-overlay.d.ts +226 -18
- package/dist/env-overlay.mjs +181 -22
- package/dist/getdotenv.cli.mjs +18166 -3437
- package/dist/index.d.ts +729 -136
- package/dist/index.mjs +18207 -3457
- package/dist/plugins-aws.d.ts +289 -104
- package/dist/plugins-aws.mjs +2462 -350
- package/dist/plugins-batch.d.ts +355 -105
- package/dist/plugins-batch.mjs +2595 -420
- package/dist/plugins-cmd.d.ts +287 -118
- package/dist/plugins-cmd.mjs +2661 -839
- package/dist/plugins-init.d.ts +272 -100
- package/dist/plugins-init.mjs +2152 -37
- package/dist/plugins.d.ts +323 -140
- package/dist/plugins.mjs +18006 -2025
- package/dist/templates/cli/index.ts +26 -0
- package/dist/templates/cli/plugins/hello.ts +43 -0
- 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 +43 -0
- package/dist/templates/index.ts +26 -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 +43 -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 +52 -89
- package/templates/cli/index.ts +26 -0
- package/templates/cli/plugins/hello.ts +43 -0
- package/templates/config/js/getdotenv.config.js +9 -4
- 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 +17 -5
- package/templates/config/yaml/public/getdotenv.config.yaml +0 -3
- package/dist/cliHost.cjs +0 -2078
- package/dist/cliHost.d.cts +0 -451
- package/dist/cliHost.d.mts +0 -451
- package/dist/config.cjs +0 -252
- package/dist/config.d.cts +0 -55
- package/dist/config.d.mts +0 -55
- package/dist/env-overlay.cjs +0 -163
- package/dist/env-overlay.d.cts +0 -50
- package/dist/env-overlay.d.mts +0 -50
- package/dist/index.cjs +0 -4077
- package/dist/index.d.cts +0 -318
- package/dist/index.d.mts +0 -318
- package/dist/plugins-aws.cjs +0 -666
- package/dist/plugins-aws.d.cts +0 -158
- package/dist/plugins-aws.d.mts +0 -158
- package/dist/plugins-batch.cjs +0 -658
- package/dist/plugins-batch.d.cts +0 -181
- package/dist/plugins-batch.d.mts +0 -181
- package/dist/plugins-cmd.cjs +0 -1112
- package/dist/plugins-cmd.d.cts +0 -178
- package/dist/plugins-cmd.d.mts +0 -178
- package/dist/plugins-demo.cjs +0 -352
- package/dist/plugins-demo.d.cts +0 -158
- package/dist/plugins-demo.d.mts +0 -158
- package/dist/plugins-demo.d.ts +0 -158
- package/dist/plugins-demo.mjs +0 -350
- package/dist/plugins-init.cjs +0 -289
- package/dist/plugins-init.d.cts +0 -162
- package/dist/plugins-init.d.mts +0 -162
- package/dist/plugins.cjs +0 -2327
- package/dist/plugins.d.cts +0 -211
- package/dist/plugins.d.mts +0 -211
- package/templates/cli/ts/index.ts +0 -9
- package/templates/cli/ts/plugins/hello.ts +0 -17
package/dist/config.d.ts
CHANGED
|
@@ -1,31 +1,188 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
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
|
+
*/
|
|
3
22
|
shell?: string | boolean;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type GetDotenvConfigResolved = {
|
|
7
|
-
dotenvToken?: string;
|
|
8
|
-
privateToken?: string;
|
|
9
|
-
paths?: string[];
|
|
23
|
+
/** Whether to load variables into `process.env`. */
|
|
10
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. */
|
|
11
38
|
log?: boolean;
|
|
12
|
-
|
|
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.
|
|
90
|
+
*/
|
|
91
|
+
interface ScriptDef<TShell extends string | boolean = string | boolean> {
|
|
92
|
+
/** The command string to execute. */
|
|
93
|
+
cmd: string;
|
|
94
|
+
/** Shell override for this script. */
|
|
95
|
+
shell?: TShell | undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Scripts table shape.
|
|
99
|
+
*/
|
|
100
|
+
type ScriptsTable<TShell extends string | boolean = string | boolean> = Record<string, string | ScriptDef<TShell>>;
|
|
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
|
+
*/
|
|
121
|
+
type Scripts = ScriptsTable;
|
|
122
|
+
|
|
123
|
+
type GetDotenvConfigResolved = {
|
|
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
|
+
*/
|
|
13
137
|
scripts?: Scripts;
|
|
138
|
+
/**
|
|
139
|
+
* Keys required to be present in the final composed environment.
|
|
140
|
+
* Validation occurs after overlays and dynamics.
|
|
141
|
+
*/
|
|
14
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
|
+
*/
|
|
15
147
|
schema?: unknown;
|
|
148
|
+
/**
|
|
149
|
+
* Public global variables (string‑only).
|
|
150
|
+
*/
|
|
16
151
|
vars?: Record<string, string>;
|
|
152
|
+
/**
|
|
153
|
+
* Public per‑environment variables (string‑only).
|
|
154
|
+
*/
|
|
17
155
|
envVars?: Record<string, Record<string, string>>;
|
|
156
|
+
/**
|
|
157
|
+
* Dynamic variable definitions (JS/TS configs only).
|
|
158
|
+
*/
|
|
18
159
|
dynamic?: unknown;
|
|
160
|
+
/**
|
|
161
|
+
* Per‑plugin configuration slices keyed by realized mount path
|
|
162
|
+
* (for example, "aws/whoami").
|
|
163
|
+
*/
|
|
19
164
|
plugins?: Record<string, unknown>;
|
|
20
165
|
};
|
|
21
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Privacy scope of a configuration file ('public' is checked into git, 'local' is gitignored).
|
|
169
|
+
*/
|
|
22
170
|
type ConfigPrivacy = 'public' | 'local';
|
|
171
|
+
/**
|
|
172
|
+
* Origin scope of a configuration file ('packaged' inside the library, 'project' in the consumer repo).
|
|
173
|
+
*/
|
|
23
174
|
type ConfigScope = 'packaged' | 'project';
|
|
24
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Represents a discovered configuration file.
|
|
177
|
+
*/
|
|
178
|
+
interface ConfigFile {
|
|
179
|
+
/** Absolute path to the config file. */
|
|
25
180
|
path: string;
|
|
181
|
+
/** Privacy scope (public vs local). */
|
|
26
182
|
privacy: ConfigPrivacy;
|
|
183
|
+
/** Origin scope (packaged vs project). */
|
|
27
184
|
scope: ConfigScope;
|
|
28
|
-
}
|
|
185
|
+
}
|
|
29
186
|
/**
|
|
30
187
|
* Discover JSON/YAML config files in the packaged root and project root.
|
|
31
188
|
* Order: packaged public → project public → project local. */
|
|
@@ -38,18 +195,39 @@ declare const discoverConfigFiles: (importMetaUrl?: string) => Promise<ConfigFil
|
|
|
38
195
|
* For JS/TS: default export is loaded; "dynamic" is allowed.
|
|
39
196
|
*/
|
|
40
197
|
declare const loadConfigFile: (filePath: string) => Promise<GetDotenvConfigResolved>;
|
|
41
|
-
|
|
198
|
+
interface ResolvedConfigSources {
|
|
199
|
+
/** Configuration from the package root (public only). */
|
|
42
200
|
packaged?: GetDotenvConfigResolved;
|
|
201
|
+
/** Configuration from the project root. */
|
|
43
202
|
project?: {
|
|
203
|
+
/** Project public configuration. */
|
|
44
204
|
public?: GetDotenvConfigResolved;
|
|
205
|
+
/** Project local configuration. */
|
|
45
206
|
local?: GetDotenvConfigResolved;
|
|
46
207
|
};
|
|
47
|
-
}
|
|
208
|
+
}
|
|
48
209
|
/**
|
|
49
210
|
* Discover and load configs into resolved shapes, ordered by scope/privacy.
|
|
50
211
|
* JSON/YAML/JS/TS supported; first match per scope/privacy applies.
|
|
51
212
|
*/
|
|
52
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
|
+
*/
|
|
53
218
|
declare const toFileUrl: (p: string) => string;
|
|
54
219
|
|
|
55
|
-
|
|
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
|
+
declare const validateEnvAgainstSources: (finalEnv: ProcessEnv, sources: ResolvedConfigSources) => string[];
|
|
231
|
+
|
|
232
|
+
export { discoverConfigFiles, loadConfigFile, resolveGetDotenvConfigSources, toFileUrl, validateEnvAgainstSources };
|
|
233
|
+
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,157 @@ 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
|
+
* - .js/.mjs/.cjs: direct import * - .ts/.mts/.cts/.tsx:
|
|
153
|
+
* 1) try direct import (if a TS loader is active),
|
|
154
|
+
* 2) esbuild bundle to a temp ESM file,
|
|
155
|
+
* 3) typescript.transpileModule fallback for simple modules.
|
|
156
|
+
*
|
|
157
|
+
* @param absPath - absolute path to source file
|
|
158
|
+
* @param cacheDirName - cache subfolder under .tsbuild
|
|
159
|
+
*/
|
|
160
|
+
const loadModuleDefault = async (absPath, cacheDirName) => {
|
|
161
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
162
|
+
const fileUrl = url.pathToFileURL(absPath).toString();
|
|
163
|
+
if (!['.ts', '.mts', '.cts', '.tsx'].includes(ext)) {
|
|
90
164
|
return importDefault(fileUrl);
|
|
91
165
|
}
|
|
92
|
-
// Try direct import first
|
|
166
|
+
// Try direct import first (TS loader active)
|
|
93
167
|
try {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
return
|
|
168
|
+
const dyn = await importDefault(fileUrl);
|
|
169
|
+
if (dyn)
|
|
170
|
+
return dyn;
|
|
97
171
|
}
|
|
98
172
|
catch {
|
|
99
|
-
/*
|
|
173
|
+
/* fall through */
|
|
100
174
|
}
|
|
101
|
-
|
|
175
|
+
const stat = await fs.stat(absPath);
|
|
176
|
+
const hash = cacheHash(absPath, stat.mtimeMs);
|
|
177
|
+
const cacheDir = path.resolve('.tsbuild', cacheDirName);
|
|
178
|
+
await fs.ensureDir(cacheDir);
|
|
179
|
+
const cacheFile = path.join(cacheDir, `${path.basename(absPath)}.${hash}.mjs`);
|
|
180
|
+
// Try esbuild
|
|
102
181
|
try {
|
|
103
182
|
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
183
|
await esbuild.build({
|
|
107
184
|
entryPoints: [absPath],
|
|
108
185
|
bundle: true,
|
|
109
186
|
platform: 'node',
|
|
110
187
|
format: 'esm',
|
|
111
188
|
target: 'node20',
|
|
112
|
-
outfile,
|
|
189
|
+
outfile: cacheFile,
|
|
113
190
|
sourcemap: false,
|
|
114
191
|
logLevel: 'silent',
|
|
115
192
|
});
|
|
116
|
-
|
|
193
|
+
const result = await importDefault(url.pathToFileURL(cacheFile).toString());
|
|
194
|
+
// Best-effort: trim older cache files for this source.
|
|
195
|
+
await cleanupOldCacheFiles(cacheDir, path.basename(absPath));
|
|
196
|
+
return result;
|
|
117
197
|
}
|
|
118
198
|
catch {
|
|
119
|
-
/*
|
|
199
|
+
/* fall through to TS transpile */
|
|
120
200
|
}
|
|
121
|
-
//
|
|
201
|
+
// TypeScript transpile fallback
|
|
122
202
|
try {
|
|
123
203
|
const ts = (await import('typescript'));
|
|
124
|
-
const
|
|
125
|
-
const out = ts.transpileModule(
|
|
204
|
+
const code = await fs.readFile(absPath, 'utf-8');
|
|
205
|
+
const out = ts.transpileModule(code, {
|
|
126
206
|
compilerOptions: {
|
|
127
207
|
module: 'ESNext',
|
|
128
208
|
target: 'ES2022',
|
|
129
209
|
moduleResolution: 'NodeNext',
|
|
130
210
|
},
|
|
131
211
|
}).outputText;
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
212
|
+
await fs.writeFile(cacheFile, out, 'utf-8');
|
|
213
|
+
const result = await importDefault(url.pathToFileURL(cacheFile).toString());
|
|
214
|
+
// Best-effort: trim older cache files for this source.
|
|
215
|
+
await cleanupOldCacheFiles(cacheDir, path.basename(absPath));
|
|
216
|
+
return result;
|
|
136
217
|
}
|
|
137
218
|
catch {
|
|
138
|
-
|
|
219
|
+
// Caller decides final error wording; rethrow for upstream mapping.
|
|
220
|
+
throw new Error(`Unable to load JS/TS module: ${absPath}. Install 'esbuild' or ensure a TS loader.`);
|
|
139
221
|
}
|
|
140
222
|
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @packageDocumentation
|
|
226
|
+
* Configuration discovery and loading for get‑dotenv. Discovers config files
|
|
227
|
+
* in the packaged root and project root, loads JSON/YAML/JS/TS documents, and
|
|
228
|
+
* validates them against Zod schemas.
|
|
229
|
+
*/
|
|
230
|
+
// Discovery candidates (first match wins per scope/privacy).
|
|
231
|
+
// Order preserves historical JSON/YAML precedence; JS/TS added afterwards.
|
|
232
|
+
const PUBLIC_FILENAMES = [
|
|
233
|
+
'getdotenv.config.json',
|
|
234
|
+
'getdotenv.config.yaml',
|
|
235
|
+
'getdotenv.config.yml',
|
|
236
|
+
'getdotenv.config.js',
|
|
237
|
+
'getdotenv.config.mjs',
|
|
238
|
+
'getdotenv.config.cjs',
|
|
239
|
+
'getdotenv.config.ts',
|
|
240
|
+
'getdotenv.config.mts',
|
|
241
|
+
'getdotenv.config.cts',
|
|
242
|
+
];
|
|
243
|
+
const LOCAL_FILENAMES = [
|
|
244
|
+
'getdotenv.config.local.json',
|
|
245
|
+
'getdotenv.config.local.yaml',
|
|
246
|
+
'getdotenv.config.local.yml',
|
|
247
|
+
'getdotenv.config.local.js',
|
|
248
|
+
'getdotenv.config.local.mjs',
|
|
249
|
+
'getdotenv.config.local.cjs',
|
|
250
|
+
'getdotenv.config.local.ts',
|
|
251
|
+
'getdotenv.config.local.mts',
|
|
252
|
+
'getdotenv.config.local.cts',
|
|
253
|
+
];
|
|
254
|
+
const isYaml = (p) => ['.yaml', '.yml'].includes(extname(p).toLowerCase());
|
|
255
|
+
const isJson = (p) => extname(p).toLowerCase() === '.json';
|
|
256
|
+
const isJsOrTs = (p) => ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(extname(p).toLowerCase());
|
|
141
257
|
/**
|
|
142
258
|
* Discover JSON/YAML config files in the packaged root and project root.
|
|
143
259
|
* Order: packaged public → project public → project local. */
|
|
@@ -190,8 +306,8 @@ const loadConfigFile = async (filePath) => {
|
|
|
190
306
|
try {
|
|
191
307
|
const abs = path.resolve(filePath);
|
|
192
308
|
if (isJsOrTs(abs)) {
|
|
193
|
-
// JS/TS support: load default export via robust pipeline.
|
|
194
|
-
const mod = await
|
|
309
|
+
// JS/TS support: load default export via shared robust pipeline.
|
|
310
|
+
const mod = await loadModuleDefault(abs, 'getdotenv-config');
|
|
195
311
|
raw = mod ?? {};
|
|
196
312
|
}
|
|
197
313
|
else {
|
|
@@ -241,7 +357,66 @@ const resolveGetDotenvConfigSources = async (importMetaUrl) => {
|
|
|
241
357
|
}
|
|
242
358
|
return result;
|
|
243
359
|
};
|
|
244
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Utility primarily for tests: create a file: URL string from a path.
|
|
362
|
+
* @param p - File path.
|
|
363
|
+
*/
|
|
245
364
|
const toFileUrl = (p) => pathToFileURL(path.resolve(p)).toString();
|
|
246
365
|
|
|
247
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Validate a composed env against config-provided validation surfaces.
|
|
368
|
+
* Precedence for validation definitions:
|
|
369
|
+
* project.local -\> project.public -\> packaged
|
|
370
|
+
*
|
|
371
|
+
* Behavior:
|
|
372
|
+
* - If a JS/TS `schema` is present, use schema.safeParse(finalEnv).
|
|
373
|
+
* - Else if `requiredKeys` is present, check presence (value !== undefined).
|
|
374
|
+
* - Returns a flat list of issue strings; caller decides warn vs fail.
|
|
375
|
+
*/
|
|
376
|
+
const validateEnvAgainstSources = (finalEnv, sources) => {
|
|
377
|
+
const pick = (getter) => {
|
|
378
|
+
const pl = sources.project?.local;
|
|
379
|
+
const pp = sources.project?.public;
|
|
380
|
+
const pk = sources.packaged;
|
|
381
|
+
return ((pl && getter(pl)) ||
|
|
382
|
+
(pp && getter(pp)) ||
|
|
383
|
+
(pk && getter(pk)) ||
|
|
384
|
+
undefined);
|
|
385
|
+
};
|
|
386
|
+
const schema = pick((cfg) => cfg['schema']);
|
|
387
|
+
if (schema &&
|
|
388
|
+
typeof schema.safeParse === 'function') {
|
|
389
|
+
try {
|
|
390
|
+
const parsed = schema.safeParse(finalEnv);
|
|
391
|
+
if (!parsed.success) {
|
|
392
|
+
// Try to render zod-style issues when available.
|
|
393
|
+
const err = parsed.error;
|
|
394
|
+
const issues = Array.isArray(err.issues) && err.issues.length > 0
|
|
395
|
+
? err.issues.map((i) => {
|
|
396
|
+
const path = Array.isArray(i.path) ? i.path.join('.') : '';
|
|
397
|
+
const msg = i.message ?? 'Invalid value';
|
|
398
|
+
return path ? `[schema] ${path}: ${msg}` : `[schema] ${msg}`;
|
|
399
|
+
})
|
|
400
|
+
: ['[schema] validation failed'];
|
|
401
|
+
return issues;
|
|
402
|
+
}
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// If schema invocation fails, surface a single diagnostic.
|
|
407
|
+
return [
|
|
408
|
+
'[schema] validation failed (unable to execute schema.safeParse)',
|
|
409
|
+
];
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const requiredKeys = pick((cfg) => cfg['requiredKeys']);
|
|
413
|
+
if (Array.isArray(requiredKeys) && requiredKeys.length > 0) {
|
|
414
|
+
const missing = requiredKeys.filter((k) => finalEnv[k] === undefined);
|
|
415
|
+
if (missing.length > 0) {
|
|
416
|
+
return missing.map((k) => `[requiredKeys] missing: ${k}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return [];
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
export { discoverConfigFiles, loadConfigFile, resolveGetDotenvConfigSources, toFileUrl, validateEnvAgainstSources };
|