dotenv-runtime 0.0.0-stage → 1.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 CHANGED
@@ -1,3 +1,1206 @@
1
- # Temporary Holding Version
1
+ # dotenv-runtime
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > A fast, validated, zero-dependency environment configuration toolkit for Node.js.
4
+
5
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-ready-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
+ [![Zero Dependencies](https://img.shields.io/badge/runtime%20dependencies-0-success)](#)
8
+ [![License](https://img.shields.io/badge/license-BSD--2--Clause-blue)](LICENSE)
9
+
10
+ `dotenv-runtime` loads `.env` files into `process.env` or an isolated configuration object while adding the features production applications commonly need:
11
+
12
+ - Async configuration loading
13
+ - Variable expansion
14
+ - Required-variable checks
15
+ - Typed schema validation
16
+ - Type coercion
17
+ - Multiple `.env` files with deterministic precedence
18
+ - Parent-directory discovery
19
+ - Isolated configuration targets
20
+ - Structured errors and load metadata
21
+ - A production-oriented command runner
22
+ - CommonJS, ESM, and TypeScript support
23
+
24
+ The default behavior remains familiar to users of [`dotenv`](https://github.com/motdotla/dotenv). Extended functionality is explicit and opt-in.
25
+
26
+ ---
27
+
28
+ ## Why dotenv-runtime?
29
+
30
+ Environment configuration usually starts simple:
31
+
32
+ ```js
33
+ require('dotenv').config()
34
+ ```
35
+
36
+ But production applications often need more:
37
+
38
+ - Multiple environment files
39
+ - Validation before startup
40
+ - Required variables
41
+ - Typed values
42
+ - Variable references
43
+ - Async file loading
44
+ - Monorepo-friendly `.env` discovery
45
+ - Isolated configuration for tests
46
+ - Strict startup failures
47
+ - A CLI for launching commands
48
+
49
+ `dotenv-runtime` provides these capabilities without adding runtime dependencies.
50
+
51
+ ### Highlights
52
+
53
+ | Feature | Supported |
54
+ | --- | :---: |
55
+ | Zero runtime dependencies | ✅ |
56
+ | CommonJS | ✅ |
57
+ | Native ESM | ✅ |
58
+ | TypeScript declarations | ✅ |
59
+ | Sync configuration | ✅ |
60
+ | Async configuration | ✅ |
61
+ | Variable expansion | ✅ |
62
+ | Required variables | ✅ |
63
+ | Schema validation | ✅ |
64
+ | Type coercion | ✅ |
65
+ | Multiple `.env` files | ✅ |
66
+ | Parent-directory discovery | ✅ |
67
+ | Isolated configuration targets | ✅ |
68
+ | Optimized parser | ✅ |
69
+ | CLI command runner | ✅ |
70
+ | Optional encrypted configuration | ✅ |
71
+
72
+ ---
73
+
74
+ ## Requirements
75
+
76
+ - **Node.js 20 or later**
77
+
78
+ ---
79
+
80
+ ## Installation
81
+
82
+ ```sh
83
+ npm install dotenv-runtime
84
+ ```
85
+
86
+ ---
87
+
88
+ # Quick Start
89
+
90
+ Create a `.env` file:
91
+
92
+ ```dotenv
93
+ APP_NAME=payments-api
94
+ PORT=3000
95
+ LOG_LEVEL=info
96
+ ```
97
+
98
+ Load it from your application:
99
+
100
+ ```js
101
+ const dotenv = require('dotenv-runtime')
102
+
103
+ const result = dotenv.config()
104
+
105
+ if (result.error) {
106
+ throw result.error
107
+ }
108
+
109
+ console.log(process.env.APP_NAME)
110
+ ```
111
+
112
+ ### ESM
113
+
114
+ ```js
115
+ import dotenv, { config } from 'dotenv-runtime'
116
+
117
+ config()
118
+ ```
119
+
120
+ ### ESM preload
121
+
122
+ If you want configuration loaded as a side effect:
123
+
124
+ ```js
125
+ import 'dotenv-runtime/config'
126
+ ```
127
+
128
+ ---
129
+
130
+ # Production Configuration
131
+
132
+ For production applications, you can combine multiple features into a single configuration pipeline.
133
+
134
+ ```js
135
+ import { config } from 'dotenv-runtime'
136
+
137
+ const result = config({
138
+ path: [
139
+ '.env.production.local',
140
+ '.env.production'
141
+ ],
142
+
143
+ expand: true,
144
+
145
+ required: [
146
+ 'DATABASE_URL',
147
+ 'PORT'
148
+ ],
149
+
150
+ strict: true,
151
+
152
+ quiet: true,
153
+
154
+ schema: {
155
+ DATABASE_URL: 'url',
156
+
157
+ PORT: {
158
+ type: 'integer',
159
+ min: 1,
160
+ max: 65535,
161
+ required: true
162
+ },
163
+
164
+ LOG_LEVEL: {
165
+ type: 'string',
166
+ enum: [
167
+ 'debug',
168
+ 'info',
169
+ 'warn',
170
+ 'error'
171
+ ],
172
+ default: 'info'
173
+ }
174
+ }
175
+ })
176
+
177
+ const {
178
+ PORT,
179
+ LOG_LEVEL
180
+ } = result.validated
181
+ ```
182
+
183
+ Environment variables written to `process.env` remain strings.
184
+
185
+ Coerced and validated values are returned separately through:
186
+
187
+ ```js
188
+ result.validated
189
+ ```
190
+
191
+ This lets you keep the familiar `.env` / `process.env` behavior while still working with properly typed configuration inside your application.
192
+
193
+ ---
194
+
195
+ # `.env` Syntax
196
+
197
+ `dotenv-runtime` supports the established `.env` syntax:
198
+
199
+ ```dotenv
200
+ # Comments and blank lines are ignored.
201
+
202
+ PLAIN=value
203
+
204
+ EMPTY=
205
+
206
+ SINGLE_QUOTED='literal value'
207
+
208
+ DOUBLE_QUOTED="supports escaped\nnewlines"
209
+
210
+ BACKTICKED=`multiline-friendly value`
211
+
212
+ export EXPORTED=value
213
+
214
+ COLON: value
215
+
216
+ INLINE=value # comment
217
+
218
+ HASH="value#inside-quotes"
219
+
220
+ MULTILINE="first line
221
+ second line"
222
+ ```
223
+
224
+ Keys may contain:
225
+
226
+ - Letters
227
+ - Numbers
228
+ - Underscores
229
+ - Periods
230
+ - Hyphens
231
+
232
+ Parsed values are strings.
233
+
234
+ Existing environment values are preserved unless:
235
+
236
+ ```js
237
+ override: true
238
+ ```
239
+
240
+ is enabled.
241
+
242
+ ---
243
+
244
+ # Core API
245
+
246
+ `dotenv-runtime` exports eight primary functions:
247
+
248
+ ```text
249
+ config()
250
+ configDotenv()
251
+ configAsync()
252
+ parse()
253
+ populate()
254
+ expand()
255
+ validate()
256
+ findUp()
257
+ ```
258
+
259
+ ---
260
+
261
+ ## `config(options?)`
262
+
263
+ Loads the configured environment files synchronously.
264
+
265
+ The pipeline is:
266
+
267
+ ```text
268
+ Load files
269
+ ↓
270
+ Parse
271
+ ↓
272
+ Expand references
273
+ ↓
274
+ Check required values
275
+ ↓
276
+ Validate schema
277
+ ↓
278
+ Commit final values
279
+ ```
280
+
281
+ Example:
282
+
283
+ ```js
284
+ const dotenv = require('dotenv-runtime')
285
+
286
+ const result = dotenv.config({
287
+ path: '.env.local',
288
+ quiet: true
289
+ })
290
+ ```
291
+
292
+ ### Return value
293
+
294
+ ```ts
295
+ interface DotenvConfigOutput<T> {
296
+ parsed: Record<string, string>
297
+ loaded: string[]
298
+ errors: Error[]
299
+ error?: Error
300
+ validated?: T
301
+ }
302
+ ```
303
+
304
+ ### Result fields
305
+
306
+ | Property | Description |
307
+ | --- | --- |
308
+ | `parsed` | Parsed configuration values |
309
+ | `loaded` | Absolute paths successfully loaded |
310
+ | `errors` | All load or processing errors in encounter order |
311
+ | `error` | Primary error for conventional single-error handling |
312
+ | `validated` | Typed configuration returned after schema validation |
313
+
314
+ `loaded` contains absolute paths for successfully read files.
315
+
316
+ `errors` contains every encountered load or processing error.
317
+
318
+ `error` provides the primary error for applications that only need conventional single-error handling.
319
+
320
+ ---
321
+
322
+ ## Configuration Options
323
+
324
+ | Option | Type | Default | Description |
325
+ | --- | --- | --- | --- |
326
+ | `path` | `string \| URL \| Array<string \| URL>` | `<cwd>/.env` | File or ordered list of files to load |
327
+ | `cwd` | `string` | `process.cwd()` | Base directory for relative paths and discovery |
328
+ | `encoding` | `BufferEncoding` | `utf8` | File encoding |
329
+ | `processEnv` | `Record<string, string>` | `process.env` | Target object receiving configuration |
330
+ | `override` | `boolean` | `false` | Replace existing values and allow later files to win |
331
+ | `quiet` | `boolean` | `false` | Suppress the injection summary |
332
+ | `debug` | `boolean` | `false` | Print diagnostic information |
333
+ | `fast` | `boolean` | `false` | Use the optimized parser |
334
+ | `expand` | `boolean` | `false` | Expand variable references before validation |
335
+ | `required` | `string \| string[]` | — | Require non-empty final values |
336
+ | `allowEmpty` | `boolean` | `false` | Allow required values to be empty |
337
+ | `schema` | `DotenvSchema` | — | Validate and coerce configuration |
338
+ | `strict` | `boolean` | `false` | Throw instead of returning processing errors |
339
+ | `searchUp` | `boolean \| string` | `false` | Search ancestor directories for `.env` or a named file |
340
+ | `stopDir` | `string` | filesystem root | Stop upward discovery at this directory |
341
+ | `secure` | `boolean` | `false` | Delegate encrypted configuration to `@dotenvx/dotenvx` |
342
+
343
+ ---
344
+
345
+ # Async Loading
346
+
347
+ ## `configAsync(options?)`
348
+
349
+ `configAsync()` provides the same configuration pipeline as `config()` while using the promise-based filesystem API.
350
+
351
+ Multiple files can be read concurrently.
352
+
353
+ ```js
354
+ import { configAsync } from 'dotenv-runtime'
355
+
356
+ const result = await configAsync({
357
+ path: [
358
+ '.env.local',
359
+ '.env'
360
+ ],
361
+
362
+ processEnv: {},
363
+
364
+ quiet: true
365
+ })
366
+ ```
367
+
368
+ Use `config()` when environment configuration is part of normal synchronous application bootstrap.
369
+
370
+ Use `configAsync()` when startup is already asynchronous, configuration files are stored on slower storage, several files need to be loaded, or you prefer promise-based initialization.
371
+
372
+ ---
373
+
374
+ # `configDotenv(options?)`
375
+
376
+ Runs the local environment-file loading pipeline directly.
377
+
378
+ `config()` normally delegates to `configDotenv()` unless secure mode is enabled.
379
+
380
+ ---
381
+
382
+ # Parsing
383
+
384
+ ## `parse(source, options?)`
385
+
386
+ Parses a string or `Buffer` without:
387
+
388
+ - Reading files
389
+ - Mutating `process.env`
390
+ - Mutating another environment object
391
+
392
+ ```js
393
+ import { parse } from 'dotenv-runtime'
394
+
395
+ const values = parse(
396
+ 'HOST=localhost\nPORT=3000'
397
+ )
398
+
399
+ console.log(values)
400
+ ```
401
+
402
+ Result:
403
+
404
+ ```js
405
+ {
406
+ HOST: 'localhost',
407
+ PORT: '3000'
408
+ }
409
+ ```
410
+
411
+ ### Optimized parser
412
+
413
+ The optimized scanner can be selected explicitly:
414
+
415
+ ```js
416
+ const values = parse(source, {
417
+ fast: true
418
+ })
419
+ ```
420
+
421
+ The scanner supports:
422
+
423
+ - BOM-prefixed files
424
+ - Multiline quoted values
425
+ - Escaped quotes
426
+ - Escaped backslashes
427
+ - Comments
428
+ - `export` prefixes
429
+ - Ordered duplicate-key behavior
430
+
431
+ ---
432
+
433
+ # Populating Configuration
434
+
435
+ ## `populate(target, values, options?)`
436
+
437
+ Copies configuration values into a target object.
438
+
439
+ It returns only the values that were actually written.
440
+
441
+ ```js
442
+ import { populate } from 'dotenv-runtime'
443
+
444
+ const target = {
445
+ PORT: '8080'
446
+ }
447
+
448
+ const populated = populate(
449
+ target,
450
+ {
451
+ PORT: '3000',
452
+ HOST: 'localhost'
453
+ },
454
+ {
455
+ override: false
456
+ }
457
+ )
458
+ ```
459
+
460
+ The result is:
461
+
462
+ ```js
463
+ // target
464
+ {
465
+ PORT: '8080',
466
+ HOST: 'localhost'
467
+ }
468
+
469
+ // populated
470
+ {
471
+ HOST: 'localhost'
472
+ }
473
+ ```
474
+
475
+ Existing properties are preserved by default, including properties whose value is `undefined`.
476
+
477
+ Special property names are assigned safely without modifying object prototypes.
478
+
479
+ ---
480
+
481
+ # Variable Expansion
482
+
483
+ ## `expand(values, options?)`
484
+
485
+ Resolves variable references and returns a new object.
486
+
487
+ The input object is never mutated.
488
+
489
+ Variable expansion **never executes shell commands**.
490
+
491
+ Example:
492
+
493
+ ```dotenv
494
+ HOST=localhost
495
+ PORT=3000
496
+
497
+ BASE_URL=http://${HOST}:${PORT}
498
+
499
+ OPTIONAL=${UNSET:-fallback}
500
+
501
+ LITERAL=\$HOST
502
+ ```
503
+
504
+ Supported expressions:
505
+
506
+ | Expression | Behavior |
507
+ | --- | --- |
508
+ | `$NAME` | Resolve `NAME` |
509
+ | `${NAME}` | Resolve `NAME` |
510
+ | `${NAME-default}` | Use `default` when `NAME` is unset |
511
+ | `${NAME:-default}` | Use `default` when `NAME` is unset or empty |
512
+ | `\$NAME` | Preserve `$NAME` literally |
513
+
514
+ ### Precedence
515
+
516
+ Existing values in `processEnv` take precedence during expansion.
517
+
518
+ Use:
519
+
520
+ ```js
521
+ const expanded = expand(values, {
522
+ processEnv: process.env,
523
+ override: true
524
+ })
525
+ ```
526
+
527
+ to prefer local values instead.
528
+
529
+ ### Circular references
530
+
531
+ Circular references throw a:
532
+
533
+ ```text
534
+ dotenv-runtime_EXPANSION_CYCLE
535
+ ```
536
+
537
+ error.
538
+
539
+ The error identifies the variables involved without exposing their values.
540
+
541
+ Shell expressions such as:
542
+
543
+ ```sh
544
+ $(command)
545
+ ```
546
+
547
+ are not executed and remain untouched.
548
+
549
+ ---
550
+
551
+ # Validation & Type Coercion
552
+
553
+ ## `validate(values, schema)`
554
+
555
+ Validates configuration against a schema and returns a new object containing the declared fields.
556
+
557
+ Values can optionally be coerced into useful JavaScript types.
558
+
559
+ ```js
560
+ import { validate } from 'dotenv-runtime'
561
+
562
+ const settings = validate(process.env, {
563
+ APP_ENV: {
564
+ type: 'string',
565
+ enum: [
566
+ 'development',
567
+ 'test',
568
+ 'production'
569
+ ],
570
+ required: true
571
+ },
572
+
573
+ PORT: {
574
+ type: 'integer',
575
+ min: 1,
576
+ max: 65535,
577
+ default: 3000
578
+ },
579
+
580
+ ENABLE_CACHE: 'boolean',
581
+
582
+ CACHE_OPTIONS: 'json',
583
+
584
+ PUBLIC_URL: 'url'
585
+ })
586
+ ```
587
+
588
+ ## Supported Types
589
+
590
+ | Type | Output | Accepted input |
591
+ | --- | --- | --- |
592
+ | `string` | `string` | Any defined value |
593
+ | `number` | `number` | Finite numeric value |
594
+ | `integer` | `number` | Safe integer |
595
+ | `boolean` | `boolean` | `true`, `false`, `1`, `0`, `yes`, `no`, `on`, `off` |
596
+ | `json` | JSON value | Valid JSON text or an existing value |
597
+ | `url` | `string` | URL accepted by the standard `URL` parser |
598
+
599
+ ## Validation Rules
600
+
601
+ A schema descriptor can define:
602
+
603
+ ```ts
604
+ interface DotenvValidationRule {
605
+ type?:
606
+ | 'string'
607
+ | 'number'
608
+ | 'integer'
609
+ | 'boolean'
610
+ | 'json'
611
+ | 'url'
612
+
613
+ required?: boolean
614
+
615
+ allowEmpty?: boolean
616
+
617
+ default?: unknown
618
+
619
+ enum?: readonly unknown[]
620
+
621
+ min?: number
622
+
623
+ max?: number
624
+
625
+ pattern?: RegExp | string
626
+
627
+ validate?:
628
+ (value: unknown) =>
629
+ boolean | string | void
630
+
631
+ transform?:
632
+ (value: unknown) => unknown
633
+ }
634
+ ```
635
+
636
+ ### Type shorthand
637
+
638
+ Instead of:
639
+
640
+ ```js
641
+ PORT: {
642
+ type: 'integer'
643
+ }
644
+ ```
645
+
646
+ you can use:
647
+
648
+ ```js
649
+ PORT: 'integer'
650
+ ```
651
+
652
+ A type shorthand marks the field as required.
653
+
654
+ Descriptor rules are optional unless:
655
+
656
+ ```js
657
+ required: true
658
+ ```
659
+
660
+ is specified.
661
+
662
+ Validation failures are collected in:
663
+
664
+ ```js
665
+ error.issues
666
+ ```
667
+
668
+ Built-in diagnostics identify configuration keys and validation rules without exposing configuration values.
669
+
670
+ ---
671
+
672
+ # Multiple `.env` Files
673
+
674
+ Multiple files can be supplied in a deterministic order:
675
+
676
+ ```js
677
+ dotenv.config({
678
+ path: [
679
+ '.env.local',
680
+ '.env'
681
+ ]
682
+ })
683
+ ```
684
+
685
+ Files are processed in exactly the order provided.
686
+
687
+ ## Default behavior
688
+
689
+ Without `override`:
690
+
691
+ 1. Existing target values are preserved.
692
+ 2. The first file defining a key wins.
693
+ 3. Later files only add keys that are still absent.
694
+
695
+ For example:
696
+
697
+ ```text
698
+ .env.local
699
+ .env
700
+ ```
701
+
702
+ means `.env.local` has priority over `.env`.
703
+
704
+ ## With `override: true`
705
+
706
+ ```js
707
+ dotenv.config({
708
+ path: [
709
+ '.env.local',
710
+ '.env'
711
+ ],
712
+ override: true
713
+ })
714
+ ```
715
+
716
+ The behavior becomes:
717
+
718
+ 1. File values replace existing target values.
719
+ 2. The last file defining a key wins.
720
+
721
+ ## Atomic configuration
722
+
723
+ Required checks and schema validation run against the final candidate environment **before** parsed values are committed.
724
+
725
+ This means a failure in required-variable validation, variable expansion, or schema validation cannot partially update the target environment.
726
+
727
+ ---
728
+
729
+ # Parent Directory Discovery
730
+
731
+ ## `findUp(filename?, options?)`
732
+
733
+ Searches upward from a directory and returns the nearest matching file.
734
+
735
+ ```js
736
+ import { findUp } from 'dotenv-runtime'
737
+
738
+ const envPath = findUp('.env', {
739
+ cwd: __dirname,
740
+ stopDir: '/workspace'
741
+ })
742
+ ```
743
+
744
+ Returns an absolute path or `undefined` when no matching file is found.
745
+
746
+ ### Configuration shortcut
747
+
748
+ ```js
749
+ dotenv.config({
750
+ searchUp: true
751
+ })
752
+ ```
753
+
754
+ searches for `.env`.
755
+
756
+ You can also specify a filename:
757
+
758
+ ```js
759
+ dotenv.config({
760
+ searchUp: '.env.production'
761
+ })
762
+ ```
763
+
764
+ This is especially useful for monorepos and applications nested inside workspace directories.
765
+
766
+ ---
767
+
768
+ # Strict Error Handling
769
+
770
+ By default, errors are returned through the configuration result.
771
+
772
+ ```js
773
+ const result = dotenv.config({
774
+ path: '.env.production'
775
+ })
776
+
777
+ if (result.error) {
778
+ console.error(result.error.message)
779
+ process.exit(1)
780
+ }
781
+ ```
782
+
783
+ For applications that prefer exceptions, enable strict mode:
784
+
785
+ ```js
786
+ dotenv.config({
787
+ path: '.env.production',
788
+ required: [
789
+ 'DATABASE_URL'
790
+ ],
791
+ strict: true
792
+ })
793
+ ```
794
+
795
+ With `strict: true`, the primary configuration failure is thrown immediately.
796
+
797
+ ---
798
+
799
+ # Isolated Configuration
800
+
801
+ You do not have to write configuration into `process.env`.
802
+
803
+ Pass your own target:
804
+
805
+ ```js
806
+ const environment = {}
807
+
808
+ const result = dotenv.config({
809
+ path: '.env.test',
810
+ processEnv: environment,
811
+ quiet: true
812
+ })
813
+ ```
814
+
815
+ This is useful for:
816
+
817
+ - Tests
818
+ - Build tools
819
+ - Multiple configuration contexts
820
+ - Libraries
821
+ - Sandboxed configuration
822
+ - Applications that should avoid global environment mutation
823
+
824
+ ---
825
+
826
+ # Command-Line Interface
827
+
828
+ `dotenv-runtime` includes a command runner for launching processes with loaded environment variables.
829
+
830
+ ## Basic usage
831
+
832
+ ```sh
833
+ dotenv-runtime run -- node server.js
834
+ ```
835
+
836
+ The `--` separator marks the beginning of the child process command.
837
+
838
+ ## Multiple files
839
+
840
+ ```sh
841
+ dotenv-runtime run \
842
+ -f .env.local \
843
+ -f .env \
844
+ -- node server.js
845
+ ```
846
+
847
+ Files are processed in the specified order.
848
+
849
+ ## Required variables
850
+
851
+ ```sh
852
+ dotenv-runtime run \
853
+ --expand \
854
+ --required DATABASE_URL,PORT \
855
+ -- node server.js
856
+ ```
857
+
858
+ ## Search parent directories
859
+
860
+ ```sh
861
+ dotenv-runtime run \
862
+ --search-up \
863
+ -- npm test
864
+ ```
865
+
866
+ ## CLI options
867
+
868
+ | Option | Description |
869
+ | --- | --- |
870
+ | `-f, --file <path>` | Load a file; repeatable |
871
+ | `--cwd <path>` | Resolve files from another directory |
872
+ | `--search-up[=<name>]` | Find `.env` or a named file in an ancestor directory |
873
+ | `--expand` | Expand variable references safely |
874
+ | `--required <names>` | Require comma-separated variables; repeatable |
875
+ | `--allow-empty` | Permit empty required values |
876
+ | `--override` | Replace existing environment values |
877
+ | `--strict` | Fail on load or validation errors |
878
+ | `--fast` | Use the optimized parser |
879
+ | `--secure` | Delegate encrypted loading to `@dotenvx/dotenvx` |
880
+ | `--debug` | Print diagnostics |
881
+ | `--quiet` | Suppress the injection summary |
882
+ | `-h, --help` | Show command help |
883
+ | `-v, --version` | Print the installed version |
884
+
885
+ The `--` separator is required.
886
+
887
+ The child process receives its arguments directly, and `dotenv-runtime` propagates the child's exit status.
888
+
889
+ ---
890
+
891
+ # Environment-Based Configuration
892
+
893
+ Configuration defaults can also be provided through environment variables.
894
+
895
+ Two prefixes are supported:
896
+
897
+ ```text
898
+ dotenv-runtime_CONFIG_
899
+ DOTENV_CONFIG_
900
+ ```
901
+
902
+ The `dotenv-runtime_CONFIG_` prefix takes precedence when both are present.
903
+
904
+ Supported variables:
905
+
906
+ ```text
907
+ PATH
908
+ ENCODING
909
+ CWD
910
+ QUIET
911
+ DEBUG
912
+ OVERRIDE
913
+ SECURE
914
+ FAST
915
+ EXPAND
916
+ STRICT
917
+ SEARCH_UP
918
+ ALLOW_EMPTY
919
+ REQUIRED
920
+ ```
921
+
922
+ `REQUIRED` accepts a comma-separated list.
923
+
924
+ Example:
925
+
926
+ ```sh
927
+ dotenv-runtime_CONFIG_PATH=.env.production \
928
+ dotenv-runtime_CONFIG_EXPAND=true \
929
+ dotenv-runtime_CONFIG_REQUIRED=DATABASE_URL,PORT \
930
+ node server.js
931
+ ```
932
+
933
+ Explicit API options and CLI flags take precedence over environment-based defaults.
934
+
935
+ ---
936
+
937
+ # Security
938
+
939
+ `dotenv-runtime` treats `.env` files as configuration data rather than executable input.
940
+
941
+ ### Safety properties
942
+
943
+ - Variable expansion never executes commands.
944
+ - Built-in validation errors do not include configuration values.
945
+ - Required-variable and schema failures are atomic.
946
+ - Special property names cannot modify object prototypes.
947
+ - The core package performs no network operations.
948
+ - The core package has no runtime dependencies.
949
+
950
+ ### Protect your `.env` files
951
+
952
+ `.env` files commonly contain credentials and other secrets.
953
+
954
+ Do not:
955
+
956
+ - Commit secrets to source control
957
+ - Log entire configuration objects
958
+ - Expose `.env` files publicly
959
+ - Share production environment files unnecessarily
960
+
961
+ Use restrictive file permissions and your deployment platform's secret-management facilities for production credentials.
962
+
963
+ ## Encrypted Configuration
964
+
965
+ Encrypted configuration is optional.
966
+
967
+ Install `@dotenvx/dotenvx` separately:
968
+
969
+ ```sh
970
+ npm install @dotenvx/dotenvx
971
+ ```
972
+
973
+ Then enable secure mode:
974
+
975
+ ```js
976
+ dotenv.config({
977
+ secure: true
978
+ })
979
+ ```
980
+
981
+ The core package does not require the encrypted configuration dependency unless secure mode is used.
982
+
983
+ ---
984
+
985
+ # TypeScript
986
+
987
+ TypeScript declarations are included with the package.
988
+
989
+ ```ts
990
+ import { config } from 'dotenv-runtime'
991
+
992
+ interface Settings {
993
+ PORT: number
994
+ ENABLE_CACHE: boolean
995
+ }
996
+
997
+ const result = config<Settings>({
998
+ schema: {
999
+ PORT: 'integer',
1000
+ ENABLE_CACHE: 'boolean'
1001
+ },
1002
+
1003
+ strict: true
1004
+ })
1005
+
1006
+ result.validated?.PORT.toFixed()
1007
+ ```
1008
+
1009
+ The generic type can describe the validated configuration returned by the loader.
1010
+
1011
+ Compatibility type aliases beginning with `Dotenv` are retained to make migration easier.
1012
+
1013
+ ---
1014
+
1015
+ # CommonJS & ESM
1016
+
1017
+ `dotenv-runtime` provides both CommonJS and native ESM builds.
1018
+
1019
+ ### CommonJS
1020
+
1021
+ ```js
1022
+ const dotenv = require('dotenv-runtime')
1023
+
1024
+ dotenv.config()
1025
+ ```
1026
+
1027
+ ### ESM
1028
+
1029
+ ```js
1030
+ import dotenv from 'dotenv-runtime'
1031
+
1032
+ dotenv.config()
1033
+ ```
1034
+
1035
+ ### Named import
1036
+
1037
+ ```js
1038
+ import { config } from 'dotenv-runtime'
1039
+
1040
+ config()
1041
+ ```
1042
+
1043
+ ### ESM preload
1044
+
1045
+ ```js
1046
+ import 'dotenv-runtime/config'
1047
+ ```
1048
+
1049
+ ---
1050
+
1051
+ # Migrating from `dotenv`
1052
+
1053
+ For basic usage, migration is intentionally simple.
1054
+
1055
+ ### Before
1056
+
1057
+ ```js
1058
+ const dotenv = require('dotenv')
1059
+
1060
+ dotenv.config()
1061
+ ```
1062
+
1063
+ ### After
1064
+
1065
+ ```js
1066
+ const dotenv = require('dotenv-runtime')
1067
+
1068
+ dotenv.config()
1069
+ ```
1070
+
1071
+ ### ESM preload
1072
+
1073
+ Before:
1074
+
1075
+ ```js
1076
+ import 'dotenv/config'
1077
+ ```
1078
+
1079
+ After:
1080
+
1081
+ ```js
1082
+ import 'dotenv-runtime/config'
1083
+ ```
1084
+
1085
+ The core defaults remain familiar:
1086
+
1087
+ - `.env` is loaded from the current working directory
1088
+ - Values are strings
1089
+ - Existing target keys are preserved
1090
+ - Multiple paths use first-file-wins behavior
1091
+ - `override: true` enables replacement
1092
+
1093
+ `dotenv-runtime` is an independent fork and is **not** the official `dotenv` distribution.
1094
+
1095
+ ---
1096
+
1097
+ # Development
1098
+
1099
+ Install the locked development dependencies:
1100
+
1101
+ ```sh
1102
+ npm ci
1103
+ ```
1104
+
1105
+ Run the complete verification pipeline:
1106
+
1107
+ ```sh
1108
+ npm test
1109
+ ```
1110
+
1111
+ The test pipeline:
1112
+
1113
+ 1. Builds every module format
1114
+ 2. Checks JavaScript style
1115
+ 3. Validates TypeScript declarations
1116
+ 4. Runs unit and integration tests
1117
+ 5. Runs CLI and parser tests
1118
+ 6. Executes sample programs
1119
+
1120
+ ## Run samples
1121
+
1122
+ ```sh
1123
+ node samples/basic/test.js
1124
+ ```
1125
+
1126
+ ```sh
1127
+ node samples/features/test.js
1128
+ ```
1129
+
1130
+ ## Verify the package before publishing
1131
+
1132
+ ```sh
1133
+ npm pack --dry-run
1134
+ ```
1135
+
1136
+ ---
1137
+
1138
+ # Project Structure
1139
+
1140
+ ```text
1141
+ .
1142
+ ├── lib/ Runtime implementation and declarations
1143
+ ├── scripts/ Reproducible build tooling
1144
+ ├── tests/ Unit, integration, CLI, parser, and type tests
1145
+ ├── samples/ Executable consumer-style examples
1146
+ └── dist/ Generated CommonJS, ESM, CLI,
1147
+ preload, and declaration files
1148
+ ```
1149
+
1150
+ ---
1151
+
1152
+ # Design Principles
1153
+
1154
+ `dotenv-runtime` is built around a few simple principles.
1155
+
1156
+ ### Familiar by default
1157
+
1158
+ Basic usage should feel immediately familiar to existing `dotenv` users.
1159
+
1160
+ ### Explicit extensions
1161
+
1162
+ Advanced behavior is opt-in rather than silently changing traditional behavior.
1163
+
1164
+ ### Deterministic configuration
1165
+
1166
+ Multiple files, overrides, expansion, and validation follow predictable rules.
1167
+
1168
+ ### No unnecessary dependencies
1169
+
1170
+ The core runtime remains dependency-free.
1171
+
1172
+ ### Safe configuration handling
1173
+
1174
+ Configuration values should be treated as data, not executable input.
1175
+
1176
+ ### Atomic startup
1177
+
1178
+ Validation should happen before configuration is committed, preventing partially configured environments.
1179
+
1180
+ ### Type-aware configuration
1181
+
1182
+ Environment variables begin as strings, but applications can explicitly validate and coerce them into useful types.
1183
+
1184
+ ---
1185
+
1186
+ # Upstream Attribution
1187
+
1188
+ `dotenv-runtime` is derived from [`dotenv`](https://github.com/motdotla/dotenv) and retains its BSD 2-Clause licensing terms.
1189
+
1190
+ This fork adds independent:
1191
+
1192
+ - APIs
1193
+ - Packaging
1194
+ - Tests
1195
+ - Documentation
1196
+ - Build tooling
1197
+ - Configuration features
1198
+ - Release policy
1199
+
1200
+ ---
1201
+
1202
+ # License
1203
+
1204
+ BSD 2-Clause.
1205
+
1206
+ See [`LICENSE`](LICENSE) for the complete license text.