@epsilon-asi/foundation 0.0.12 → 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.
@@ -2,7 +2,8 @@ import * as z from "zod";
2
2
  import { DiscoverOptions, FileLayer, LayeredLoadOptions, LayeredResult, LoadOptions, SafeResult } from "./types";
3
3
  /**
4
4
  * Find every existing `fileName` (a name or a relative path such as `.tool/config.yaml`) from
5
- * `startDir` upwards, then among `fallbacks`. Results are ordered highest precedence first.
5
+ * `startDir` upwards, then among `fallbacks`, after any `extraFiles`. Results are ordered highest
6
+ * precedence first.
6
7
  */
7
8
  export declare function discoverLayers(fileName: string, options?: DiscoverOptions): Promise<FileLayer[]>;
8
9
  export declare function discoverLayersSync(fileName: string, options?: DiscoverOptions): FileLayer[];
@@ -15,7 +16,7 @@ export declare function loadMergedFiles<S extends z.ZodType>(filePaths: readonly
15
16
  export declare function loadMergedFilesSync<S extends z.ZodType>(filePaths: readonly string[], schema: S, options?: LoadOptions): z.output<S>;
16
17
  /**
17
18
  * Discover every `fileName` layer (see discoverLayers), merge them and validate.
18
- * Precedence: start directory > nearer ancestors > farther ancestors > fallbacks, in order.
19
+ * Precedence: extraFiles (in order) > start directory > nearer ancestors > farther ancestors > fallbacks (in order).
19
20
  * With no layers found, the schema validates `{}`, so its defaults apply.
20
21
  */
21
22
  export declare function loadLayeredFile<S extends z.ZodType>(fileName: string, schema: S, options?: LayeredLoadOptions): Promise<LayeredResult<z.output<S>>>;
@@ -26,38 +26,44 @@ const node_path_1 = __importDefault(require("node:path"));
26
26
  const types_1 = require("./types");
27
27
  const util_1 = require("./util");
28
28
  /**
29
- * Every place `fileName` may live, highest precedence first: the start directory, each ancestor up to
30
- * `stopDir` (or the root), then the fallbacks. Duplicates keep their highest-precedence slot.
29
+ * Every place `fileName` may live, highest precedence first: the extra files, the start directory, each
30
+ * ancestor up to `stopDir` (or the root), then the fallbacks. Duplicates keep their highest-precedence slot.
31
31
  */
32
32
  function candidates(fileName, options) {
33
- var _a, _b;
33
+ var _a, _b, _c;
34
34
  const start = node_path_1.default.resolve((0, util_1.expandHome)((_a = options.startDir) !== null && _a !== void 0 ? _a : process.cwd()));
35
35
  const stopAt = options.stopDir === undefined ? undefined : node_path_1.default.resolve((0, util_1.expandHome)(options.stopDir));
36
- const all = [];
36
+ const all = ((_b = options.extraFiles) !== null && _b !== void 0 ? _b : []).map((extra) => ({
37
+ filePath: node_path_1.default.resolve(start, (0, util_1.expandHome)(extra)),
38
+ origin: "extra",
39
+ }));
37
40
  for (let dir = start;; dir = node_path_1.default.dirname(dir)) {
38
41
  all.push({ filePath: node_path_1.default.join(dir, fileName), origin: dir === start ? "local" : "ancestor" });
39
42
  if (dir === stopAt || dir === node_path_1.default.dirname(dir))
40
43
  break;
41
44
  }
42
- for (const fallback of (_b = options.fallbacks) !== null && _b !== void 0 ? _b : []) {
45
+ for (const fallback of (_c = options.fallbacks) !== null && _c !== void 0 ? _c : []) {
43
46
  all.push({ filePath: node_path_1.default.resolve((0, util_1.expandHome)(fallback)), origin: "fallback" });
44
47
  }
45
48
  const seen = new Set();
46
49
  return all.filter(({ filePath }) => !seen.has(filePath) && Boolean(seen.add(filePath)));
47
50
  }
51
+ /** Extra files are kept whether or not they exist, so a missing one is reported when it loads. */
52
+ const isRequested = (layer) => layer.origin === "extra";
48
53
  /**
49
54
  * Find every existing `fileName` (a name or a relative path such as `.tool/config.yaml`) from
50
- * `startDir` upwards, then among `fallbacks`. Results are ordered highest precedence first.
55
+ * `startDir` upwards, then among `fallbacks`, after any `extraFiles`. Results are ordered highest
56
+ * precedence first.
51
57
  */
52
58
  function discoverLayers(fileName_1) {
53
59
  return __awaiter(this, arguments, void 0, function* (fileName, options = {}) {
54
60
  const all = candidates(fileName, options);
55
- const present = yield Promise.all(all.map(({ filePath }) => (0, util_1.pathExists)(filePath)));
61
+ const present = yield Promise.all(all.map((layer) => isRequested(layer) || (0, util_1.pathExists)(layer.filePath)));
56
62
  return all.filter((_, i) => present[i]);
57
63
  });
58
64
  }
59
65
  function discoverLayersSync(fileName, options = {}) {
60
- return candidates(fileName, options).filter(({ filePath }) => (0, node_fs_1.existsSync)(filePath));
66
+ return candidates(fileName, options).filter((layer) => isRequested(layer) || (0, node_fs_1.existsSync)(layer.filePath));
61
67
  }
62
68
  /** A layer must be a mapping to merge; an empty file contributes nothing. */
63
69
  function asLayer(raw, filePath) {
@@ -87,7 +93,7 @@ function loadMergedFilesSync(filePaths, schema, options = {}) {
87
93
  }
88
94
  /**
89
95
  * Discover every `fileName` layer (see discoverLayers), merge them and validate.
90
- * Precedence: start directory > nearer ancestors > farther ancestors > fallbacks, in order.
96
+ * Precedence: extraFiles (in order) > start directory > nearer ancestors > farther ancestors > fallbacks (in order).
91
97
  * With no layers found, the schema validates `{}`, so its defaults apply.
92
98
  */
93
99
  function loadLayeredFile(fileName_1, schema_1) {
@@ -105,8 +105,8 @@ 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 was found: the start directory, one of its ancestors, or a fallback path. */
109
- export type LayerOrigin = "local" | "ancestor" | "fallback";
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
110
  /** One file discovered by discoverLayers(). */
111
111
  export interface FileLayer {
112
112
  /** Absolute path of the file. */
@@ -114,6 +114,12 @@ export interface FileLayer {
114
114
  origin: LayerOrigin;
115
115
  }
116
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[];
117
123
  /** Directory the upward walk starts from. Default: process.cwd(). */
118
124
  startDir?: string;
119
125
  /** Last directory the walk visits (inclusive). Default: the filesystem root. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epsilon-asi/foundation",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "",
5
5
  "mainDir": "./",
6
6
  "types": "./build/index.d.ts",