molex-env 0.3.2 → 0.3.3
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 +25 -14
- package/index.d.ts +134 -0
- package/package.json +46 -37
- package/src/index.js +27 -26
- package/src/lib/apply.js +61 -75
- package/src/lib/cast.js +133 -124
- package/src/lib/core.js +161 -148
- package/src/lib/errors.js +66 -98
- package/src/lib/files.js +35 -36
- package/src/lib/parser.js +96 -115
- package/src/lib/schema.js +48 -59
- package/src/lib/utils.js +19 -21
- package/src/lib/watch.js +128 -110
package/src/lib/cast.js
CHANGED
|
@@ -1,124 +1,133 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { invalidTypeError } = require('./errors');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
* @param {
|
|
54
|
-
* @
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { invalidTypeError } = require('./errors');
|
|
4
|
+
|
|
5
|
+
/* ------------------------------------------------------------------ */
|
|
6
|
+
/* Constants */
|
|
7
|
+
/* ------------------------------------------------------------------ */
|
|
8
|
+
|
|
9
|
+
const CAST_ALL_ON = { boolean: true, number: true, json: true, date: true };
|
|
10
|
+
const CAST_ALL_OFF = { boolean: false, number: false, json: false, date: false };
|
|
11
|
+
const NUMBER_RE = /^-?\d+(\.\d+)?$/;
|
|
12
|
+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:[T\s].*)?$/;
|
|
13
|
+
|
|
14
|
+
/* ------------------------------------------------------------------ */
|
|
15
|
+
/* Helpers */
|
|
16
|
+
/* ------------------------------------------------------------------ */
|
|
17
|
+
|
|
18
|
+
/** @returns {boolean} */
|
|
19
|
+
function isNumber(value)
|
|
20
|
+
{
|
|
21
|
+
return NUMBER_RE.test(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @returns {boolean} */
|
|
25
|
+
function isIsoDate(value)
|
|
26
|
+
{
|
|
27
|
+
return ISO_DATE_RE.test(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ------------------------------------------------------------------ */
|
|
31
|
+
/* Public API */
|
|
32
|
+
/* ------------------------------------------------------------------ */
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Normalize cast options into explicit booleans.
|
|
36
|
+
* @param {boolean|{boolean?: boolean, number?: boolean, json?: boolean, date?: boolean}} cast
|
|
37
|
+
* @returns {{boolean: boolean, number: boolean, json: boolean, date: boolean}}
|
|
38
|
+
*/
|
|
39
|
+
function normalizeCast(cast)
|
|
40
|
+
{
|
|
41
|
+
if (cast === true || cast === undefined) return { ...CAST_ALL_ON };
|
|
42
|
+
if (cast === false) return { ...CAST_ALL_OFF };
|
|
43
|
+
return {
|
|
44
|
+
boolean: cast.boolean !== false,
|
|
45
|
+
number: cast.number !== false,
|
|
46
|
+
json: cast.json !== false,
|
|
47
|
+
date: cast.date !== false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Coerce a raw string to the requested schema type.
|
|
53
|
+
* @param {string} raw
|
|
54
|
+
* @param {string} type
|
|
55
|
+
* @param {string} file
|
|
56
|
+
* @param {number} line
|
|
57
|
+
* @returns {any}
|
|
58
|
+
*/
|
|
59
|
+
function coerceType(raw, type, file, line)
|
|
60
|
+
{
|
|
61
|
+
switch (type)
|
|
62
|
+
{
|
|
63
|
+
case 'string':
|
|
64
|
+
return raw;
|
|
65
|
+
|
|
66
|
+
case 'boolean': {
|
|
67
|
+
const lower = raw.toLowerCase();
|
|
68
|
+
if (lower === 'true') return true;
|
|
69
|
+
if (lower === 'false') return false;
|
|
70
|
+
throw invalidTypeError('boolean', raw, file, line);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case 'number':
|
|
74
|
+
if (!isNumber(raw)) throw invalidTypeError('number', raw, file, line);
|
|
75
|
+
return Number(raw);
|
|
76
|
+
|
|
77
|
+
case 'json':
|
|
78
|
+
return JSON.parse(raw);
|
|
79
|
+
|
|
80
|
+
case 'date': {
|
|
81
|
+
const date = new Date(raw);
|
|
82
|
+
if (Number.isNaN(date.getTime())) throw invalidTypeError('date', raw, file, line);
|
|
83
|
+
return date;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
default:
|
|
87
|
+
return raw;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Auto-cast a raw string based on enabled casting rules.
|
|
93
|
+
* @param {string} raw
|
|
94
|
+
* @param {{boolean: boolean, number: boolean, json: boolean, date: boolean}} cast
|
|
95
|
+
* @returns {any}
|
|
96
|
+
*/
|
|
97
|
+
function autoCast(raw, cast)
|
|
98
|
+
{
|
|
99
|
+
const trimmed = raw.trim();
|
|
100
|
+
|
|
101
|
+
if (cast.boolean)
|
|
102
|
+
{
|
|
103
|
+
const lower = trimmed.toLowerCase();
|
|
104
|
+
if (lower === 'true') return true;
|
|
105
|
+
if (lower === 'false') return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (cast.number && isNumber(trimmed))
|
|
109
|
+
{
|
|
110
|
+
return Number(trimmed);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (cast.json && (trimmed.startsWith('{') || trimmed.startsWith('[')))
|
|
114
|
+
{
|
|
115
|
+
try
|
|
116
|
+
{
|
|
117
|
+
return JSON.parse(trimmed);
|
|
118
|
+
} catch (_err)
|
|
119
|
+
{
|
|
120
|
+
return trimmed;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (cast.date && isIsoDate(trimmed))
|
|
125
|
+
{
|
|
126
|
+
const date = new Date(trimmed);
|
|
127
|
+
if (!Number.isNaN(date.getTime())) return date;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return trimmed;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { normalizeCast, coerceType, autoCast };
|
package/src/lib/core.js
CHANGED
|
@@ -1,148 +1,161 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
|
-
const { normalizeCast } = require('./cast');
|
|
6
|
-
const { normalizeSchema, applySchemaDefaults } = require('./schema');
|
|
7
|
-
const { parseEntries } = require('./parser');
|
|
8
|
-
const { applyEntry } = require('./apply');
|
|
9
|
-
const { resolveFiles } = require('./files');
|
|
10
|
-
const { deepFreeze } = require('./utils');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
*
|
|
109
|
-
* @param {object} options
|
|
110
|
-
* @returns {{parsed: object, origins: object, files: string[]}}
|
|
111
|
-
*/
|
|
112
|
-
function
|
|
113
|
-
{
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const { normalizeCast } = require('./cast');
|
|
6
|
+
const { normalizeSchema, applySchemaDefaults } = require('./schema');
|
|
7
|
+
const { parseEntries } = require('./parser');
|
|
8
|
+
const { applyEntry } = require('./apply');
|
|
9
|
+
const { resolveFiles } = require('./files');
|
|
10
|
+
const { deepFreeze } = require('./utils');
|
|
11
|
+
|
|
12
|
+
/* ------------------------------------------------------------------ */
|
|
13
|
+
/* Internal helpers (shared pipeline) */
|
|
14
|
+
/* ------------------------------------------------------------------ */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a fresh processing state container.
|
|
18
|
+
* @returns {{values: object, raw: object, origins: object, seenPerFile: Map}}
|
|
19
|
+
*/
|
|
20
|
+
function createState()
|
|
21
|
+
{
|
|
22
|
+
return {
|
|
23
|
+
values: {},
|
|
24
|
+
raw: {},
|
|
25
|
+
origins: {},
|
|
26
|
+
seenPerFile: new Map(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Normalize user options into a consistent internal format.
|
|
32
|
+
* @param {object} options - Raw user options.
|
|
33
|
+
* @returns {object} Normalized processing options.
|
|
34
|
+
*/
|
|
35
|
+
function normalizeOptions(options)
|
|
36
|
+
{
|
|
37
|
+
return {
|
|
38
|
+
schema: normalizeSchema(options.schema),
|
|
39
|
+
cast: normalizeCast(options.cast),
|
|
40
|
+
strict: Boolean(options.strict),
|
|
41
|
+
freeze: options.freeze !== false,
|
|
42
|
+
onWarning: options.onWarning,
|
|
43
|
+
debug: options.debug,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Process a block of .menv text into the state.
|
|
49
|
+
* @param {object} state
|
|
50
|
+
* @param {string} text
|
|
51
|
+
* @param {string} filePath
|
|
52
|
+
* @param {object} opts - Normalized options.
|
|
53
|
+
*/
|
|
54
|
+
function processText(state, text, filePath, opts)
|
|
55
|
+
{
|
|
56
|
+
const entries = parseEntries(text, { strict: opts.strict, filePath });
|
|
57
|
+
for (const entry of entries)
|
|
58
|
+
{
|
|
59
|
+
applyEntry(state, entry, opts, filePath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Apply schema defaults and optionally deep-freeze the result.
|
|
65
|
+
* @param {object} state
|
|
66
|
+
* @param {object} opts - Normalized options.
|
|
67
|
+
*/
|
|
68
|
+
function finalizeState(state, opts)
|
|
69
|
+
{
|
|
70
|
+
applySchemaDefaults(state.values, state.origins, opts.schema, opts.strict);
|
|
71
|
+
if (opts.freeze) deepFreeze(state.values);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ------------------------------------------------------------------ */
|
|
75
|
+
/* Side-effect helpers */
|
|
76
|
+
/* ------------------------------------------------------------------ */
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Write parsed values to process.env when enabled.
|
|
80
|
+
* @param {object} values
|
|
81
|
+
* @param {object} options - Raw user options.
|
|
82
|
+
*/
|
|
83
|
+
function exportToEnv(values, options)
|
|
84
|
+
{
|
|
85
|
+
if (!options.exportEnv) return;
|
|
86
|
+
for (const [key, value] of Object.entries(values))
|
|
87
|
+
{
|
|
88
|
+
if (!options.override && Object.prototype.hasOwnProperty.call(process.env, key)) continue;
|
|
89
|
+
process.env[key] = value === undefined ? '' : String(value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Attach parsed values to process.menv unless disabled.
|
|
95
|
+
* @param {object} values
|
|
96
|
+
* @param {object} options - Raw user options.
|
|
97
|
+
*/
|
|
98
|
+
function attachToProcess(values, options)
|
|
99
|
+
{
|
|
100
|
+
if (options.attach !== false) process.menv = values;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ------------------------------------------------------------------ */
|
|
104
|
+
/* Public API */
|
|
105
|
+
/* ------------------------------------------------------------------ */
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Load .menv files, merge, parse, and validate.
|
|
109
|
+
* @param {object} [options]
|
|
110
|
+
* @returns {{parsed: object, raw: object, origins: object, files: string[]}}
|
|
111
|
+
*/
|
|
112
|
+
function load(options = {})
|
|
113
|
+
{
|
|
114
|
+
const opts = normalizeOptions(options);
|
|
115
|
+
const state = createState();
|
|
116
|
+
const filePaths = resolveFiles(options);
|
|
117
|
+
const readFiles = [];
|
|
118
|
+
|
|
119
|
+
for (const filePath of filePaths)
|
|
120
|
+
{
|
|
121
|
+
if (!fs.existsSync(filePath)) continue;
|
|
122
|
+
processText(state, fs.readFileSync(filePath, 'utf8'), filePath, opts);
|
|
123
|
+
readFiles.push(filePath);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
finalizeState(state, opts);
|
|
127
|
+
exportToEnv(state.values, options);
|
|
128
|
+
attachToProcess(state.values, options);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
parsed: state.values,
|
|
132
|
+
raw: state.raw,
|
|
133
|
+
origins: state.origins,
|
|
134
|
+
files: readFiles,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Parse a string of .menv content without loading files.
|
|
140
|
+
* @param {string} text
|
|
141
|
+
* @param {object} [options]
|
|
142
|
+
* @returns {{parsed: object, raw: object, origins: object, files: string[]}}
|
|
143
|
+
*/
|
|
144
|
+
function parse(text, options = {})
|
|
145
|
+
{
|
|
146
|
+
const opts = normalizeOptions(options);
|
|
147
|
+
const state = createState();
|
|
148
|
+
const filePath = options.filePath || '<inline>';
|
|
149
|
+
|
|
150
|
+
processText(state, text, filePath, opts);
|
|
151
|
+
finalizeState(state, opts);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
parsed: state.values,
|
|
155
|
+
raw: state.raw,
|
|
156
|
+
origins: state.origins,
|
|
157
|
+
files: options.filePath ? [options.filePath] : [],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { load, parse };
|