@epsilon-asi/foundation 0.0.11 → 0.0.13
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/build/decorators/environment/defaultSystem.d.ts +6 -0
- package/build/decorators/environment/defaultSystem.js +12 -0
- package/build/decorators/environment/dotenv.d.ts +48 -0
- package/build/decorators/environment/dotenv.js +77 -0
- package/build/decorators/environment/errors.d.ts +12 -0
- package/build/decorators/environment/errors.js +13 -0
- package/build/decorators/environment/index.d.ts +7 -164
- package/build/decorators/environment/index.js +19 -221
- package/build/decorators/environment/parse.d.ts +9 -0
- package/build/decorators/environment/parse.js +88 -0
- package/build/decorators/environment/system.d.ts +4 -0
- package/build/decorators/environment/system.js +133 -0
- package/build/decorators/environment/types.d.ts +160 -0
- package/build/decorators/environment/types.js +2 -0
- package/build/files/index.d.ts +4 -2
- package/build/files/index.js +13 -1
- package/build/files/layered.d.ts +45 -0
- package/build/files/layered.js +139 -0
- package/build/files/load.js +5 -26
- package/build/files/types.d.ts +31 -0
- package/build/files/util.d.ts +15 -1
- package/build/files/util.js +78 -1
- package/package.json +6 -5
package/build/files/load.js
CHANGED
|
@@ -13,42 +13,21 @@ exports.loadFile = loadFile;
|
|
|
13
13
|
exports.loadFileSync = loadFileSync;
|
|
14
14
|
exports.safeLoadFile = safeLoadFile;
|
|
15
15
|
exports.safeLoadFileSync = safeLoadFileSync;
|
|
16
|
-
const types_1 = require("./types");
|
|
17
16
|
const util_1 = require("./util");
|
|
18
17
|
/** Read a JSON/YAML file and return a validated, typed value. */
|
|
19
18
|
function loadFile(filePath_1, schema_1) {
|
|
20
19
|
return __awaiter(this, arguments, void 0, function* (filePath, schema, options = {}) {
|
|
21
|
-
|
|
22
|
-
const { raw, format } = (0, util_1.toRaw)(yield (0, util_1.readTextAsync)(filePath, opts), opts);
|
|
23
|
-
return (0, util_1.validateAsync)(schema, yield (0, util_1.runPreprocess)(raw, format, opts), opts);
|
|
20
|
+
return (0, util_1.validateAsync)(schema, yield (0, util_1.readRawAsync)(filePath, options), Object.assign(Object.assign({}, options), { filePath }));
|
|
24
21
|
});
|
|
25
22
|
}
|
|
26
23
|
/** Synchronous variant. Fails if the schema contains async refinements/transforms. */
|
|
27
24
|
function loadFileSync(filePath, schema, options = {}) {
|
|
28
|
-
|
|
29
|
-
const { raw, format } = (0, util_1.toRaw)((0, util_1.readTextSync)(filePath, opts), opts);
|
|
30
|
-
return (0, util_1.validateSync)(schema, (0, util_1.runPreprocessSync)(raw, format, opts), opts);
|
|
25
|
+
return (0, util_1.validateSync)(schema, (0, util_1.readRawSync)(filePath, options), Object.assign(Object.assign({}, options), { filePath }));
|
|
31
26
|
}
|
|
32
27
|
/** Like loadFile, but returns a result object instead of throwing SchemaFileError. */
|
|
33
|
-
function safeLoadFile(
|
|
34
|
-
return
|
|
35
|
-
try {
|
|
36
|
-
return { success: true, data: yield loadFile(filePath, schema, options) };
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
if (error instanceof types_1.SchemaFileError)
|
|
40
|
-
return { success: false, error };
|
|
41
|
-
throw error;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
28
|
+
function safeLoadFile(filePath, schema, options = {}) {
|
|
29
|
+
return (0, util_1.safely)(() => loadFile(filePath, schema, options));
|
|
44
30
|
}
|
|
45
31
|
function safeLoadFileSync(filePath, schema, options = {}) {
|
|
46
|
-
|
|
47
|
-
return { success: true, data: loadFileSync(filePath, schema, options) };
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
if (error instanceof types_1.SchemaFileError)
|
|
51
|
-
return { success: false, error };
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
32
|
+
return (0, util_1.safelySync)(() => loadFileSync(filePath, schema, options));
|
|
54
33
|
}
|
package/build/files/types.d.ts
CHANGED
|
@@ -105,4 +105,35 @@ export interface SaveOptions extends Omit<SerializeOptions, "format" | "filePath
|
|
|
105
105
|
/** File permission bits for the written file, e.g. 0o600 for secrets. */
|
|
106
106
|
mode?: number;
|
|
107
107
|
}
|
|
108
|
+
/** Where a layer came from: an extra file, the start directory, one of its ancestors, or a fallback path. */
|
|
109
|
+
export type LayerOrigin = "extra" | "local" | "ancestor" | "fallback";
|
|
110
|
+
/** One file discovered by discoverLayers(). */
|
|
111
|
+
export interface FileLayer {
|
|
112
|
+
/** Absolute path of the file. */
|
|
113
|
+
filePath: string;
|
|
114
|
+
origin: LayerOrigin;
|
|
115
|
+
}
|
|
116
|
+
export interface DiscoverOptions {
|
|
117
|
+
/**
|
|
118
|
+
* Additional files to layer in, ranked above everything discovered; index 0 has the highest
|
|
119
|
+
* precedence. Relative paths resolve against `startDir`, and `~/` is expanded. They are always
|
|
120
|
+
* included, so loading a missing one fails with a "read" error unless `allowMissing` is set.
|
|
121
|
+
*/
|
|
122
|
+
extraFiles?: readonly string[];
|
|
123
|
+
/** Directory the upward walk starts from. Default: process.cwd(). */
|
|
124
|
+
startDir?: string;
|
|
125
|
+
/** Last directory the walk visits (inclusive). Default: the filesystem root. */
|
|
126
|
+
stopDir?: string;
|
|
127
|
+
/** Paths checked after the walk, in order; they rank below every directory match. `~/` is expanded. */
|
|
128
|
+
fallbacks?: readonly string[];
|
|
129
|
+
}
|
|
130
|
+
/** Options for loadLayeredFile(): discovery, plus load options applied to every layer. */
|
|
131
|
+
export interface LayeredLoadOptions extends Omit<LoadOptions, "filePath">, DiscoverOptions {
|
|
132
|
+
}
|
|
133
|
+
export interface LayeredResult<T> {
|
|
134
|
+
/** The merged, validated value. */
|
|
135
|
+
value: T;
|
|
136
|
+
/** The layers that were merged, highest precedence first. */
|
|
137
|
+
layers: FileLayer[];
|
|
138
|
+
}
|
|
108
139
|
export declare const EXTENSIONS: Record<string, FileFormat>;
|
package/build/files/util.d.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
import { FileFormat, LoadOptions, ParseOptions, SaveOptions, SerializeOptions } from "./types";
|
|
2
|
+
import { FileFormat, LoadOptions, ParseOptions, SafeResult, SaveOptions, SerializeOptions } from "./types";
|
|
3
3
|
declare const MISSING: unique symbol;
|
|
4
4
|
export declare function label(filePath?: string): string;
|
|
5
5
|
export declare function isPlainObject(v: unknown): v is Record<string, unknown>;
|
|
6
|
+
/**
|
|
7
|
+
* Recursively merge plain objects; later sources win. `undefined` values and non-object sources are
|
|
8
|
+
* skipped, arrays and scalars are replaced (not concatenated), and no source is mutated.
|
|
9
|
+
*/
|
|
10
|
+
export declare function deepMerge<T extends Record<string, unknown> = Record<string, unknown>>(...sources: readonly unknown[]): T;
|
|
11
|
+
/** Replace a leading `~` with the current user's home directory. */
|
|
12
|
+
export declare function expandHome(filePath: string): string;
|
|
13
|
+
export declare function pathExists(filePath: string): Promise<boolean>;
|
|
14
|
+
/** Run `run`, turning a thrown SchemaFileError into a failed SafeResult. Other errors propagate. */
|
|
15
|
+
export declare function safely<T>(run: () => Promise<T>): Promise<SafeResult<T>>;
|
|
16
|
+
export declare function safelySync<T>(run: () => T): SafeResult<T>;
|
|
6
17
|
export declare function decodeText(text: string, format: FileFormat, opts: ParseOptions): unknown;
|
|
7
18
|
export declare function formatFromExtension(filePath: string): FileFormat | undefined;
|
|
8
19
|
/**
|
|
@@ -16,6 +27,9 @@ export declare function toRaw(text: string | typeof MISSING, opts: LoadOptions):
|
|
|
16
27
|
};
|
|
17
28
|
export declare function readTextAsync(filePath: string, opts: LoadOptions): Promise<string | typeof MISSING>;
|
|
18
29
|
export declare function readTextSync(filePath: string, opts: LoadOptions): string | typeof MISSING;
|
|
30
|
+
/** Read, decode and preprocess a file: everything loadFile does short of validation. */
|
|
31
|
+
export declare function readRawAsync(filePath: string, options: LoadOptions): Promise<unknown>;
|
|
32
|
+
export declare function readRawSync(filePath: string, options: LoadOptions): unknown;
|
|
19
33
|
export declare function runPreprocess(raw: unknown, format: FileFormat, opts: ParseOptions): Promise<unknown>;
|
|
20
34
|
export declare function runPreprocessSync(raw: unknown, format: FileFormat, opts: ParseOptions): unknown;
|
|
21
35
|
export declare function validateAsync<S extends z.ZodType>(schema: S, raw: unknown, opts: ParseOptions): Promise<z.output<S>>;
|
package/build/files/util.js
CHANGED
|
@@ -47,12 +47,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
48
|
exports.label = label;
|
|
49
49
|
exports.isPlainObject = isPlainObject;
|
|
50
|
+
exports.deepMerge = deepMerge;
|
|
51
|
+
exports.expandHome = expandHome;
|
|
52
|
+
exports.pathExists = pathExists;
|
|
53
|
+
exports.safely = safely;
|
|
54
|
+
exports.safelySync = safelySync;
|
|
50
55
|
exports.decodeText = decodeText;
|
|
51
56
|
exports.formatFromExtension = formatFromExtension;
|
|
52
57
|
exports.resolveFormat = resolveFormat;
|
|
53
58
|
exports.toRaw = toRaw;
|
|
54
59
|
exports.readTextAsync = readTextAsync;
|
|
55
60
|
exports.readTextSync = readTextSync;
|
|
61
|
+
exports.readRawAsync = readRawAsync;
|
|
62
|
+
exports.readRawSync = readRawSync;
|
|
56
63
|
exports.runPreprocess = runPreprocess;
|
|
57
64
|
exports.runPreprocessSync = runPreprocessSync;
|
|
58
65
|
exports.validateAsync = validateAsync;
|
|
@@ -65,6 +72,7 @@ exports.writeTextSync = writeTextSync;
|
|
|
65
72
|
const YAML = __importStar(require("yaml"));
|
|
66
73
|
const z = __importStar(require("zod"));
|
|
67
74
|
const promises_1 = require("node:fs/promises");
|
|
75
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
68
76
|
const node_path_1 = __importDefault(require("node:path"));
|
|
69
77
|
const node_fs_1 = require("node:fs");
|
|
70
78
|
const types_1 = require("./types");
|
|
@@ -81,6 +89,62 @@ function isPlainObject(v) {
|
|
|
81
89
|
const proto = Object.getPrototypeOf(v);
|
|
82
90
|
return proto === Object.prototype || proto === null;
|
|
83
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Recursively merge plain objects; later sources win. `undefined` values and non-object sources are
|
|
94
|
+
* skipped, arrays and scalars are replaced (not concatenated), and no source is mutated.
|
|
95
|
+
*/
|
|
96
|
+
function deepMerge(...sources) {
|
|
97
|
+
const result = {};
|
|
98
|
+
for (const source of sources) {
|
|
99
|
+
if (!isPlainObject(source))
|
|
100
|
+
continue;
|
|
101
|
+
for (const [key, value] of Object.entries(source)) {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
continue;
|
|
104
|
+
const existing = result[key];
|
|
105
|
+
// defineProperty, not assignment: a parsed "__proto__" key must stay data, not swap the prototype.
|
|
106
|
+
Object.defineProperty(result, key, {
|
|
107
|
+
value: isPlainObject(existing) && isPlainObject(value) ? deepMerge(existing, value) : value,
|
|
108
|
+
enumerable: true,
|
|
109
|
+
writable: true,
|
|
110
|
+
configurable: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
/** Replace a leading `~` with the current user's home directory. */
|
|
117
|
+
function expandHome(filePath) {
|
|
118
|
+
if (filePath === "~")
|
|
119
|
+
return node_os_1.default.homedir();
|
|
120
|
+
return /^~[\\/]/.test(filePath) ? node_path_1.default.join(node_os_1.default.homedir(), filePath.slice(2)) : filePath;
|
|
121
|
+
}
|
|
122
|
+
function pathExists(filePath) {
|
|
123
|
+
return (0, promises_1.access)(filePath).then(() => true, () => false);
|
|
124
|
+
}
|
|
125
|
+
/** Run `run`, turning a thrown SchemaFileError into a failed SafeResult. Other errors propagate. */
|
|
126
|
+
function safely(run) {
|
|
127
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
try {
|
|
129
|
+
return { success: true, data: yield run() };
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error instanceof types_1.SchemaFileError)
|
|
133
|
+
return { success: false, error };
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function safelySync(run) {
|
|
139
|
+
try {
|
|
140
|
+
return { success: true, data: run() };
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error instanceof types_1.SchemaFileError)
|
|
144
|
+
return { success: false, error };
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
84
148
|
function isNotFound(e) {
|
|
85
149
|
return (e === null || e === void 0 ? void 0 : e.code) === "ENOENT";
|
|
86
150
|
}
|
|
@@ -147,6 +211,19 @@ function readTextSync(filePath, opts) {
|
|
|
147
211
|
throw new types_1.SchemaFileError("read", `Failed to read ${filePath}: ${errMsg(cause)}`, { filePath, cause });
|
|
148
212
|
}
|
|
149
213
|
}
|
|
214
|
+
/** Read, decode and preprocess a file: everything loadFile does short of validation. */
|
|
215
|
+
function readRawAsync(filePath, options) {
|
|
216
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
217
|
+
const opts = Object.assign(Object.assign({}, options), { filePath });
|
|
218
|
+
const { raw, format } = toRaw(yield readTextAsync(filePath, opts), opts);
|
|
219
|
+
return runPreprocess(raw, format, opts);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function readRawSync(filePath, options) {
|
|
223
|
+
const opts = Object.assign(Object.assign({}, options), { filePath });
|
|
224
|
+
const { raw, format } = toRaw(readTextSync(filePath, opts), opts);
|
|
225
|
+
return runPreprocessSync(raw, format, opts);
|
|
226
|
+
}
|
|
150
227
|
function runPreprocess(raw, format, opts) {
|
|
151
228
|
return __awaiter(this, void 0, void 0, function* () {
|
|
152
229
|
if (!opts.preprocess)
|
|
@@ -348,7 +425,7 @@ function writeTextAsync(filePath, text, opts) {
|
|
|
348
425
|
try {
|
|
349
426
|
if (opts.createDirs !== false)
|
|
350
427
|
yield (0, promises_1.mkdir)(node_path_1.default.dirname(filePath), { recursive: true });
|
|
351
|
-
if (opts.overwrite === false && (yield (
|
|
428
|
+
if (opts.overwrite === false && (yield pathExists(filePath))) {
|
|
352
429
|
throw new types_1.SchemaFileError("write", `Refusing to overwrite existing file ${filePath}`, { filePath });
|
|
353
430
|
}
|
|
354
431
|
const base = { encoding: (_a = opts.encoding) !== null && _a !== void 0 ? _a : "utf8", mode: opts.mode };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epsilon-asi/foundation",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "",
|
|
5
5
|
"mainDir": "./",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"testEnvironment": "node",
|
|
32
32
|
"coverageThreshold": {
|
|
33
33
|
"global": {
|
|
34
|
-
"branches":
|
|
35
|
-
"functions":
|
|
36
|
-
"lines":
|
|
37
|
-
"statements":
|
|
34
|
+
"branches": 90,
|
|
35
|
+
"functions": 90,
|
|
36
|
+
"lines": 90,
|
|
37
|
+
"statements": 90
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"testMatch": [
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"author": "",
|
|
74
74
|
"license": "SEE LICENSE IN LICENSE",
|
|
75
75
|
"dependencies": {
|
|
76
|
+
"dotenv": "^17.4.2",
|
|
76
77
|
"yaml": "^2.9.1"
|
|
77
78
|
},
|
|
78
79
|
"peerDependencies": {
|