@osdlc/tiered-dirs 0.0.0-stage → 0.3.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/CHANGELOG.md +99 -0
- package/CONTRIBUTING.md +66 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +422 -2
- package/SECURITY.md +23 -0
- package/dist/base-dirs.d.ts +311 -0
- package/dist/base-dirs.js +477 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.js +20 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +24 -0
- package/dist/segment.d.ts +17 -0
- package/dist/segment.js +40 -0
- package/dist/tiered-dirs.d.ts +190 -0
- package/dist/tiered-dirs.js +237 -0
- package/package.json +64 -3
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The horizontal axis: how durable a piece of state is. The vertical axis —
|
|
3
|
+
* who shares it — is the tier (see `tiered-dirs.ts`).
|
|
4
|
+
*
|
|
5
|
+
* A caller names both: `suite('notes').cache()`.
|
|
6
|
+
*
|
|
7
|
+
* Exhaustive on purpose, as in the Rust twin: a new home changes every
|
|
8
|
+
* caller's layout, so adding one is a breaking (0.x minor) release.
|
|
9
|
+
*
|
|
10
|
+
* Stable in 0.x.
|
|
11
|
+
*/
|
|
12
|
+
export type Home = 'durable' | 'config' | 'cache' | 'state' | 'logs' | 'runtime';
|
|
13
|
+
/**
|
|
14
|
+
* Every {@link Home}, in table order.
|
|
15
|
+
*
|
|
16
|
+
* Stable in 0.x.
|
|
17
|
+
*/
|
|
18
|
+
export declare const HOMES: readonly Home[];
|
|
19
|
+
/**
|
|
20
|
+
* Which per-OS table the homes come from when no override moves them. The two
|
|
21
|
+
* differ on macOS only: `native` is Apple's places, `xdg` is the XDG Base
|
|
22
|
+
* Directory rules. Linux uses XDG under both, and on Windows `xdg` means
|
|
23
|
+
* `native`. A CLI or developer tool uses `xdg`; a GUI app uses `native`. The
|
|
24
|
+
* Rust twin's `Convention` has the same two.
|
|
25
|
+
*
|
|
26
|
+
* A minor release may add a convention, as the Rust `Convention` is
|
|
27
|
+
* `#[non_exhaustive]`. A `switch` over it needs a `default` arm.
|
|
28
|
+
*
|
|
29
|
+
* Stable in 0.x.
|
|
30
|
+
*/
|
|
31
|
+
export type Convention = 'native' | 'xdg';
|
|
32
|
+
/**
|
|
33
|
+
* Every {@link Convention}. The first is the default.
|
|
34
|
+
*
|
|
35
|
+
* Stable in 0.x.
|
|
36
|
+
*/
|
|
37
|
+
export declare const CONVENTIONS: readonly Convention[];
|
|
38
|
+
/**
|
|
39
|
+
* Parse a {@link Convention}: exactly `native` or `xdg`, in lower case,
|
|
40
|
+
* untrimmed. The Rust twin's `Convention::from_str` is the same rule.
|
|
41
|
+
*
|
|
42
|
+
* Throws {@link TieredDirsError} with code `INVALID_CONVENTION` for any other
|
|
43
|
+
* value. {@link EnvVars.convention} is read differently: it is trimmed first.
|
|
44
|
+
*
|
|
45
|
+
* Stable in 0.x.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseConvention(value: string): Convention;
|
|
48
|
+
/**
|
|
49
|
+
* The per-OS roots the whole package is built on.
|
|
50
|
+
*
|
|
51
|
+
* This is the injectable seam. Nothing below it reads the environment: a
|
|
52
|
+
* `TieredDirs` resolves its roots once, at construction, and every path after that
|
|
53
|
+
* is pure string work. That is what lets a server that cannot read the
|
|
54
|
+
* environment hand in roots from its own configuration, while a command-line
|
|
55
|
+
* program builds them from the environment.
|
|
56
|
+
*
|
|
57
|
+
* Every field is `readonly`: build a new value to change a root, as
|
|
58
|
+
* {@link rootedBaseDirs} and the `overrides` option do.
|
|
59
|
+
*
|
|
60
|
+
* Stable in 0.x.
|
|
61
|
+
*/
|
|
62
|
+
export interface BaseDirs {
|
|
63
|
+
/** Long-lived state that must survive a cache purge. */
|
|
64
|
+
readonly durable: string;
|
|
65
|
+
/** User-editable configuration. */
|
|
66
|
+
readonly config: string;
|
|
67
|
+
/** Regenerable data. The OS may delete this at any time. */
|
|
68
|
+
readonly cache: string;
|
|
69
|
+
/** State that should outlive a restart but is not user data: history, trust markers. */
|
|
70
|
+
readonly state: string;
|
|
71
|
+
/** Log files. Never folded into `cache` — the OS purges cache. */
|
|
72
|
+
readonly logs: string;
|
|
73
|
+
/** Pidfiles, sockets, locks. Never under `cache`; see {@link systemBaseDirs}. */
|
|
74
|
+
readonly runtime: string;
|
|
75
|
+
/**
|
|
76
|
+
* The platform temp directory. Not a {@link Home}: nothing here outlives the
|
|
77
|
+
* call that wrote it, so it is untiered and reached through `TieredDirs.scratch()`
|
|
78
|
+
* rather than through a tier.
|
|
79
|
+
*/
|
|
80
|
+
readonly scratch: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The names of the environment variables that move base directories. Build one
|
|
84
|
+
* with {@link envVars}, which derives every name from a prefix.
|
|
85
|
+
*
|
|
86
|
+
* A minor release may add a name, as the Rust `EnvVars` is
|
|
87
|
+
* `#[non_exhaustive]`. An object built by hand would then miss it, so build
|
|
88
|
+
* one with {@link envVars}. `dirs[home]` is the Rust `EnvVars::dir`.
|
|
89
|
+
*
|
|
90
|
+
* Stable in 0.x.
|
|
91
|
+
*/
|
|
92
|
+
export interface EnvVars {
|
|
93
|
+
/**
|
|
94
|
+
* Moves every base directory not moved by its own variable into the
|
|
95
|
+
* {@link rootedBaseDirs} layout under the named root.
|
|
96
|
+
*/
|
|
97
|
+
readonly root: string;
|
|
98
|
+
/** The variable that moves one base directory. */
|
|
99
|
+
readonly dirs: Readonly<Record<keyof BaseDirs, string>>;
|
|
100
|
+
/**
|
|
101
|
+
* When set (non-empty), {@link systemBaseDirs} throws instead of falling back
|
|
102
|
+
* to an OS home. A test runner sets it, and a child process inherits it.
|
|
103
|
+
*/
|
|
104
|
+
readonly requireOverride: string;
|
|
105
|
+
/**
|
|
106
|
+
* Picks the {@link Convention}, over the one the caller passed: `native` or
|
|
107
|
+
* `xdg`. Moves no directory itself.
|
|
108
|
+
*/
|
|
109
|
+
readonly convention: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* {@link EnvVars} named from one prefix: `<P>_ROOT`, `<P>_DURABLE_DIR`,
|
|
113
|
+
* `<P>_CONFIG_DIR`, `<P>_CACHE_DIR`, `<P>_STATE_DIR`, `<P>_LOGS_DIR`,
|
|
114
|
+
* `<P>_RUNTIME_DIR`, `<P>_SCRATCH_DIR`, `<P>_REQUIRE_OVERRIDE` and
|
|
115
|
+
* `<P>_CONVENTION`. The Rust twin's `env_vars!` derives the same names.
|
|
116
|
+
*
|
|
117
|
+
* Stable in 0.x.
|
|
118
|
+
*/
|
|
119
|
+
export declare function envVars(prefix: string): EnvVars;
|
|
120
|
+
/**
|
|
121
|
+
* Every variable that moves a base directory: the root and the seven
|
|
122
|
+
* per-directory names. Excludes {@link EnvVars.requireOverride} and
|
|
123
|
+
* {@link EnvVars.convention}, which move nothing.
|
|
124
|
+
*
|
|
125
|
+
* Stable in 0.x.
|
|
126
|
+
*/
|
|
127
|
+
export declare function overrideVars(vars: EnvVars): readonly string[];
|
|
128
|
+
/**
|
|
129
|
+
* Whether `env` sets any of {@link overrideVars} to a non-empty value. The
|
|
130
|
+
* Rust twin's `EnvVars::is_overridden` is the same test.
|
|
131
|
+
*
|
|
132
|
+
* Stable in 0.x.
|
|
133
|
+
*/
|
|
134
|
+
export declare function isOverridden(vars: EnvVars, env?: Record<string, string | undefined>): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Every base directory under one stub root: `<root>/durable`, `<root>/config`,
|
|
137
|
+
* `<root>/cache`, `<root>/state`, `<root>/logs`, `<root>/scratch`, and runtime
|
|
138
|
+
* at `<root>/durable/run`.
|
|
139
|
+
*
|
|
140
|
+
* The test and parity-fixture seam, and the layout {@link EnvVars.root} selects. The
|
|
141
|
+
* Rust twin's `BaseDirs::rooted` has the same layout, so a fixture row asserts
|
|
142
|
+
* identical absolute paths on both arms.
|
|
143
|
+
*
|
|
144
|
+
* Stable in 0.x.
|
|
145
|
+
*/
|
|
146
|
+
export declare function rootedBaseDirs(root: string): BaseDirs;
|
|
147
|
+
/**
|
|
148
|
+
* How {@link systemBaseDirs} and `TieredDirs.fromEnv` resolve. Every input is
|
|
149
|
+
* optional and injectable. The Rust twin's `ResolveOptions` has `convention`
|
|
150
|
+
* and `testRoot`; `platform`, `env`, `home` and `overrides` are this twin's
|
|
151
|
+
* own seams.
|
|
152
|
+
*
|
|
153
|
+
* Stable in 0.x.
|
|
154
|
+
*/
|
|
155
|
+
export interface ResolveOptions {
|
|
156
|
+
/** Defaults to `process.platform`. */
|
|
157
|
+
platform?: string;
|
|
158
|
+
/**
|
|
159
|
+
* The per-OS table to use. Defaults to `native`. {@link EnvVars.convention}
|
|
160
|
+
* beats it when set.
|
|
161
|
+
*/
|
|
162
|
+
convention?: Convention;
|
|
163
|
+
/**
|
|
164
|
+
* Defaults to `process.env`. Injecting this means the supplied environment
|
|
165
|
+
* is the whole story: the host's real home directory is not consulted. The
|
|
166
|
+
* override variables are read from it too.
|
|
167
|
+
*/
|
|
168
|
+
env?: Record<string, string | undefined>;
|
|
169
|
+
/** Home directory. Overrides `HOME` / `USERPROFILE` when given. */
|
|
170
|
+
home?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Replace individual roots, ahead of the environment. An overridden root is
|
|
173
|
+
* taken as-is and needs no home directory. It is not a base for the others:
|
|
174
|
+
* overriding `durable` here leaves `runtime` and `logs` where the
|
|
175
|
+
* environment and the per-OS table put them.
|
|
176
|
+
*/
|
|
177
|
+
overrides?: Partial<BaseDirs>;
|
|
178
|
+
/**
|
|
179
|
+
* Whether an unset root variable reads as {@link testRoot} in a test runner
|
|
180
|
+
* process. Defaults to `true`. With `false`, resolution runs as it would
|
|
181
|
+
* outside a test runner: {@link EnvVars.root} and
|
|
182
|
+
* {@link EnvVars.requireOverride} still apply. Has no effect when `env` or
|
|
183
|
+
* `home` is injected, since neither test rule runs then.
|
|
184
|
+
*/
|
|
185
|
+
testRoot?: boolean;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolve the base directories. Each takes the first answer in this order:
|
|
189
|
+
*
|
|
190
|
+
* 1. `opts.overrides`.
|
|
191
|
+
* 2. The root's own variable, `vars.dirs[key]` ({@link EnvVars.dirs}).
|
|
192
|
+
* 3. `vars.root` ({@link EnvVars.root}) — the {@link rootedBaseDirs} layout.
|
|
193
|
+
* 4. The per-OS table below, for the convention: {@link EnvVars.convention}
|
|
194
|
+
* when set, else `opts.convention`, else `native`. The variable is
|
|
195
|
+
* trimmed, then must be exactly `native` or `xdg`; anything else throws.
|
|
196
|
+
*
|
|
197
|
+
* Runtime follows durable: when the runtime variable is unset but durable is
|
|
198
|
+
* moved by step 2 or 3, runtime is `<durable>/run`, never the OS runtime
|
|
199
|
+
* directory. An empty variable counts as unset; a relative one throws. The
|
|
200
|
+
* Rust twin's `BaseDirs::from_env` resolves steps 2–4 the same way, held to it
|
|
201
|
+
* by the parity fixture's `env_cases`.
|
|
202
|
+
*
|
|
203
|
+
* The per-OS table this package owns so no caller has to. Linux, and macOS
|
|
204
|
+
* under `xdg`, use the XDG column; Windows uses its column under both.
|
|
205
|
+
*
|
|
206
|
+
* | home | macOS, `native` | XDG (Linux/other; macOS `xdg`) | Windows |
|
|
207
|
+
* |---------|--------------------------------|---------------------------------|----------------------|
|
|
208
|
+
* | durable | `~/Library/Application Support`| `$XDG_DATA_HOME`, `~/.local/share` | `%APPDATA%`, `<home>/AppData/Roaming` |
|
|
209
|
+
* | config | `~/Library/Application Support`| `$XDG_CONFIG_HOME`, `~/.config` | `%APPDATA%`, `<home>/AppData/Roaming` |
|
|
210
|
+
* | cache | `~/Library/Caches` | `$XDG_CACHE_HOME`, `~/.cache` | `%LOCALAPPDATA%`, `<home>/AppData/Local` |
|
|
211
|
+
* | state | `~/Library/Application Support`| `$XDG_STATE_HOME`, `~/.local/state` | `<local>/state`, where `<local>` is the cache cell |
|
|
212
|
+
* | logs | `~/Library/Logs` | `$XDG_STATE_HOME`, `~/.local/state` | `<durable>/logs` |
|
|
213
|
+
* | runtime | `<durable>/run` | `$XDG_RUNTIME_DIR`, `<durable>/run` | `<durable>/run` |
|
|
214
|
+
* | scratch | `$TMPDIR`, `/tmp` | `$TMPDIR`, `/tmp` | `%TEMP%`, `%TMP%`, `<home>/AppData/Local/Temp` |
|
|
215
|
+
*
|
|
216
|
+
* A relative `XDG_*` value is ignored, as the XDG spec says, and `~` is not
|
|
217
|
+
* expanded. Under XDG, logs and state share one directory.
|
|
218
|
+
*
|
|
219
|
+
* Two of those cells need care:
|
|
220
|
+
*
|
|
221
|
+
* - `logs` has no cross-platform library equivalent. macOS wants
|
|
222
|
+
* `~/Library/Logs`; where the OS offers nothing, logs land at
|
|
223
|
+
* `<durable>/logs`. They are never folded into `cache`.
|
|
224
|
+
* - `runtime` is real only under the XDG table (Linux, and macOS under
|
|
225
|
+
* `xdg`), and only when `XDG_RUNTIME_DIR` is set. Everywhere else it
|
|
226
|
+
* resolves under `<durable>/run` — deliberately not under cache, because the
|
|
227
|
+
* OS may purge cache while the process a pidfile describes is still alive.
|
|
228
|
+
* State is never cache either: on Windows it is `<local>/state`, beside
|
|
229
|
+
* each tier's cache folder. The reserved segment names keep them apart.
|
|
230
|
+
*
|
|
231
|
+
* `scratch` is resolved from the injected environment rather than by calling
|
|
232
|
+
* `os.tmpdir()`, which reads the real `process.env` and would make an injected
|
|
233
|
+
* environment stop being the whole story. The Rust twin asks its own platform
|
|
234
|
+
* the same question; the two roots need not match, because parity pins only
|
|
235
|
+
* what sits below a root.
|
|
236
|
+
*
|
|
237
|
+
* Throws {@link TieredDirsError}. Its `code` is `MISSING_BASE_DIR` when a
|
|
238
|
+
* root falls back to the home directory and none can be found,
|
|
239
|
+
* `RELATIVE_OVERRIDE` when an override variable is not absolute,
|
|
240
|
+
* `INVALID_CONVENTION` when {@link EnvVars.convention} names no convention,
|
|
241
|
+
* and `OVERRIDE_REQUIRED` as the second test rule below says. Headless CI and
|
|
242
|
+
* sandboxes do not always have a home directory.
|
|
243
|
+
*
|
|
244
|
+
* Two test rules run first, and only when neither `env` nor `home` is
|
|
245
|
+
* injected (an injected environment is the whole story):
|
|
246
|
+
*
|
|
247
|
+
* - In a test runner process with the root variable unset and `HOME` not
|
|
248
|
+
* moved, the root reads as {@link testRoot}:
|
|
249
|
+
* `<tmp>/tiered-dirs-test-<pid>-<start ms>`, unique per process and
|
|
250
|
+
* cached per module instance (per test file under Vitest's default isolation).
|
|
251
|
+
* `opts.testRoot: false` turns this rule off; the next one still applies.
|
|
252
|
+
* A test runner process has a test file as its entry point and runs
|
|
253
|
+
* under `NODE_ENV=test`, `NODE_TEST_CONTEXT` (set by `node --test`), Bun, or
|
|
254
|
+
* a Vitest worker. Bun counts on its own because `bun test` keeps a
|
|
255
|
+
* `NODE_ENV` that is already set. A Vitest worker has `VITEST_WORKER_ID` or
|
|
256
|
+
* `VITEST_POOL_ID` set, and its entry point is a file in the `dist/workers`
|
|
257
|
+
* folder of the `vitest` package (Vitest 4 and later) or tinypool's
|
|
258
|
+
* `dist/entry/process.js` or `dist/entry/worker.js` (Vitest 3 and earlier),
|
|
259
|
+
* not the test file, so a worker counts by that entry point instead of the
|
|
260
|
+
* test-file check. A child a test spawns
|
|
261
|
+
* inherits the Vitest variables but has a different entry point, so it gets
|
|
262
|
+
* no default. A test that points `HOME` at a fixture, in-process or for a child,
|
|
263
|
+
* keeps it.
|
|
264
|
+
* - With {@link EnvVars.requireOverride} set, a root that would fall back to
|
|
265
|
+
* the per-OS table throws a {@link TieredDirsError} naming the fix.
|
|
266
|
+
*
|
|
267
|
+
* The Rust twin's `BaseDirs::from_env` applies the same two rules, with two
|
|
268
|
+
* differences. It detects a test by its cargo test binary rather than by the
|
|
269
|
+
* environment. And the "`HOME` not moved" condition exists only here: a
|
|
270
|
+
* TypeScript test can point `HOME` at a fixture to redirect the home, which a
|
|
271
|
+
* Rust test cannot do through `dirs`, so the TypeScript rule yields to it.
|
|
272
|
+
* The cost is that a CI job whose `HOME` differs from the passwd home, such as
|
|
273
|
+
* a GitHub Actions container job (`HOME=/github/home`, passwd home `/root`),
|
|
274
|
+
* gets no test-root default. Set `<P>_ROOT` there.
|
|
275
|
+
*
|
|
276
|
+
* Stable in 0.x.
|
|
277
|
+
*/
|
|
278
|
+
export declare function systemBaseDirs(vars: EnvVars, opts?: ResolveOptions): BaseDirs;
|
|
279
|
+
/**
|
|
280
|
+
* `<tmp>/tiered-dirs-test-<pid>-<start ms>` when this process is a test runner
|
|
281
|
+
* process, else `undefined`. Resolved once per module instance: once per test
|
|
282
|
+
* file under Vitest's default isolation, and once per installed copy of the
|
|
283
|
+
* package.
|
|
284
|
+
*
|
|
285
|
+
* A test runner process is the one {@link systemBaseDirs} describes: a test
|
|
286
|
+
* file as its entry point under `NODE_ENV=test`, `NODE_TEST_CONTEXT` or Bun,
|
|
287
|
+
* or a Vitest worker. A child a test spawns is not one.
|
|
288
|
+
*
|
|
289
|
+
* The start time keeps a later process that reuses the pid off this one's
|
|
290
|
+
* leftover state. Callers that read the environment themselves, such as a
|
|
291
|
+
* product-tier layer, use this to apply the same default. The Rust twin's
|
|
292
|
+
* `test_root()` is the same per-process root, detected by its cargo test
|
|
293
|
+
* binary.
|
|
294
|
+
*
|
|
295
|
+
* Stable in 0.x.
|
|
296
|
+
*/
|
|
297
|
+
export declare function testRoot(): string | undefined;
|
|
298
|
+
/** Whether this process is a test runner running a test file (see {@link systemBaseDirs}). */
|
|
299
|
+
export declare function isTestRunnerProcess(env: Record<string, string | undefined>, entry?: string): boolean;
|
|
300
|
+
/**
|
|
301
|
+
* The XDG layout under `home`, with the `XDG_*` variables read from `env`: the
|
|
302
|
+
* `xdg` table, on any host. Each home is its `XDG_*` variable when that holds
|
|
303
|
+
* an absolute path, else its default under `home`. Logs share the state home.
|
|
304
|
+
* Scratch is `$TMPDIR`, else `/tmp`.
|
|
305
|
+
*
|
|
306
|
+
* The Rust twin's `BaseDirs::xdg` builds the same layout, held to it by the
|
|
307
|
+
* parity fixture's `convention_cases`.
|
|
308
|
+
*
|
|
309
|
+
* Stable in 0.x.
|
|
310
|
+
*/
|
|
311
|
+
export declare function xdgBaseDirs(home: string, env: Record<string, string | undefined>): BaseDirs;
|