@effected/xdg 0.1.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/AppDirs.js +208 -0
- package/LICENSE +21 -0
- package/NativeDirs.js +71 -0
- package/README.md +140 -0
- package/Xdg.js +189 -0
- package/XdgConfig.js +115 -0
- package/index.d.ts +392 -0
- package/index.js +6 -0
- package/package.json +47 -0
- package/tsdoc-metadata.json +11 -0
package/AppDirs.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { NativeDirs } from "./NativeDirs.js";
|
|
2
|
+
import { CurrentPlatform, Xdg } from "./Xdg.js";
|
|
3
|
+
import { Context, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/AppDirs.ts
|
|
6
|
+
/**
|
|
7
|
+
* The four directory kinds XDG separates, plus the runtime directory.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
const AppDirKind = Schema.Literals([
|
|
12
|
+
"config",
|
|
13
|
+
"data",
|
|
14
|
+
"cache",
|
|
15
|
+
"state",
|
|
16
|
+
"runtime"
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Indicates that an application directory could not be created.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* The only way `AppDirs` fails. Resolution cannot fail — it happens once, at
|
|
23
|
+
* layer construction, from an environment that is already resolved — so this
|
|
24
|
+
* error means exactly one thing: the `mkdir -p` did not work. `directory` says
|
|
25
|
+
* which kind, `path` says where, and `cause` carries the underlying
|
|
26
|
+
* `PlatformError` structurally. v3 carried `reason: String(e)` and a `directory`
|
|
27
|
+
* that could also be the string `"all"`.
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
var AppDirsError = class extends Schema.TaggedErrorClass()("AppDirsError", {
|
|
32
|
+
/** Which directory kind failed. */
|
|
33
|
+
directory: AppDirKind,
|
|
34
|
+
/** The path that could not be created. */
|
|
35
|
+
path: Schema.String,
|
|
36
|
+
/** The underlying failure, preserved structurally. */
|
|
37
|
+
cause: Schema.Defect()
|
|
38
|
+
}) {
|
|
39
|
+
get message() {
|
|
40
|
+
return `Failed to create the ${this.directory} directory at "${this.path}"`;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The fully resolved, app-namespaced directories.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
var ResolvedAppDirs = class extends Schema.Class("ResolvedAppDirs")({
|
|
49
|
+
/** The app's configuration directory. */
|
|
50
|
+
config: Schema.String,
|
|
51
|
+
/** The app's data directory. */
|
|
52
|
+
data: Schema.String,
|
|
53
|
+
/** The app's cache directory. */
|
|
54
|
+
cache: Schema.String,
|
|
55
|
+
/** The app's state directory. */
|
|
56
|
+
state: Schema.String,
|
|
57
|
+
/**
|
|
58
|
+
* The app's runtime directory.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* Absent unless `$XDG_RUNTIME_DIR` is set or `dirs.runtime` overrides it.
|
|
62
|
+
* There is no defensible fallback for a runtime directory — it must be
|
|
63
|
+
* user-owned, mode 0700 and cleaned on logout — so inventing one would be a
|
|
64
|
+
* lie, and the key is simply absent.
|
|
65
|
+
*/
|
|
66
|
+
runtime: Schema.optionalKey(Schema.String),
|
|
67
|
+
/**
|
|
68
|
+
* Where to **look** for configuration, in priority order.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* The app's own config directory, then each `$XDG_CONFIG_DIRS` entry
|
|
72
|
+
* namespaced. This is the half of the XDG spec v3 ignored entirely, and it is
|
|
73
|
+
* what makes {@link XdgConfig.resolver} a real search rather than a single stat.
|
|
74
|
+
*/
|
|
75
|
+
configSearchPath: Schema.Array(Schema.String),
|
|
76
|
+
/** Where to look for data files, in priority order. */
|
|
77
|
+
dataSearchPath: Schema.Array(Schema.String)
|
|
78
|
+
}) {};
|
|
79
|
+
/**
|
|
80
|
+
* Resolve one directory kind through the five-level precedence.
|
|
81
|
+
*
|
|
82
|
+
* 1. an explicit `dirs.<kind>` override;
|
|
83
|
+
* 2. the XDG environment variable, namespaced (`$XDG_CONFIG_HOME/<ns>`);
|
|
84
|
+
* 3. the native directory, when `native: true` and the platform has one;
|
|
85
|
+
* 4. `$HOME/<fallbackDir>` — all four kinds collapse to the one directory;
|
|
86
|
+
* 5. `$HOME/.<namespace>`.
|
|
87
|
+
*
|
|
88
|
+
* Rungs 4 and 5 are deliberately **not** the XDG spec's per-kind defaults
|
|
89
|
+
* (`~/.config`, `~/.local/share`, …). This is inherited v3 behaviour and is kept:
|
|
90
|
+
* a CLI wanting spec defaults passes them as `dirs` overrides. The deviation is
|
|
91
|
+
* documented rather than left for a reader to discover.
|
|
92
|
+
*/
|
|
93
|
+
const resolveDir = (input) => {
|
|
94
|
+
const { override, xdgHome, native, options, home, path } = input;
|
|
95
|
+
if (override !== void 0) return override;
|
|
96
|
+
if (xdgHome !== void 0) return path.join(xdgHome, options.namespace);
|
|
97
|
+
if (Option.isSome(native)) return native.value;
|
|
98
|
+
if (options.fallbackDir !== void 0) return path.join(home, options.fallbackDir);
|
|
99
|
+
return path.join(home, `.${options.namespace}`);
|
|
100
|
+
};
|
|
101
|
+
/** The app's own directory, then each system directory, namespaced. Nearest first. */
|
|
102
|
+
const searchPath = (own, systemDirs, namespace, path) => [own, ...systemDirs.map((dir) => path.join(dir, namespace))];
|
|
103
|
+
const resolveAll = (options, paths, platform, path) => {
|
|
104
|
+
const native = options.native ? NativeDirs.resolve({
|
|
105
|
+
platform,
|
|
106
|
+
namespace: options.namespace,
|
|
107
|
+
paths,
|
|
108
|
+
path
|
|
109
|
+
}) : Option.none();
|
|
110
|
+
const forKind = (xdgHome, nativeDir, override) => resolveDir({
|
|
111
|
+
override,
|
|
112
|
+
xdgHome,
|
|
113
|
+
native: Option.map(native, nativeDir),
|
|
114
|
+
options,
|
|
115
|
+
home: paths.home,
|
|
116
|
+
path
|
|
117
|
+
});
|
|
118
|
+
const config = forKind(paths.configHome, (n) => n.config, options.dirs?.config);
|
|
119
|
+
const data = forKind(paths.dataHome, (n) => n.data, options.dirs?.data);
|
|
120
|
+
const cache = forKind(paths.cacheHome, (n) => n.cache, options.dirs?.cache);
|
|
121
|
+
const state = forKind(paths.stateHome, (n) => n.state, options.dirs?.state);
|
|
122
|
+
const runtime = options.dirs?.runtime ?? (paths.runtimeDir !== void 0 ? path.join(paths.runtimeDir, options.namespace) : void 0);
|
|
123
|
+
return ResolvedAppDirs.make({
|
|
124
|
+
config,
|
|
125
|
+
data,
|
|
126
|
+
cache,
|
|
127
|
+
state,
|
|
128
|
+
...runtime !== void 0 && { runtime },
|
|
129
|
+
configSearchPath: searchPath(config, paths.configDirs, options.namespace, path),
|
|
130
|
+
dataSearchPath: searchPath(data, paths.dataDirs, options.namespace, path)
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* A namespace is a single path component. Anything else escapes the app's own
|
|
135
|
+
* directories, so it **dies** rather than resolving somewhere surprising. This
|
|
136
|
+
* is wiring, not input: only code supplies a namespace, so a bad one is a
|
|
137
|
+
* programmer error and belongs on the defect channel, not in `E`.
|
|
138
|
+
*/
|
|
139
|
+
const badNamespace = (namespace) => {
|
|
140
|
+
if (namespace.length === 0) return /* @__PURE__ */ new Error("AppDirs.layer: `namespace` must not be empty");
|
|
141
|
+
if (/[/\\]/.test(namespace) || namespace === "." || namespace === "..") return /* @__PURE__ */ new Error(`AppDirs.layer: \`namespace\` must be a single path component, received ${JSON.stringify(namespace)}`);
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* App-namespaced XDG directories, with on-demand creation.
|
|
145
|
+
*
|
|
146
|
+
* @remarks
|
|
147
|
+
* `AppDirs.layer` is a layer-**returning function**: calling it twice builds two
|
|
148
|
+
* independent services. Bind its result to a const and provide that const, per
|
|
149
|
+
* the layer memoization discipline.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const AppDirsLayer = AppDirs.layer({ namespace: "myapp", native: true });
|
|
154
|
+
* const XdgLayer = Layer.mergeAll(Xdg.layer, AppDirsLayer.pipe(Layer.provide(Xdg.layer)));
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @public
|
|
158
|
+
*/
|
|
159
|
+
var AppDirs = class AppDirs extends Context.Service()("@effected/xdg/AppDirs") {
|
|
160
|
+
/**
|
|
161
|
+
* Resolve the namespace's directories against the ambient {@link Xdg}
|
|
162
|
+
* environment and platform.
|
|
163
|
+
*
|
|
164
|
+
* @remarks
|
|
165
|
+
* The error channel is `never`. The one failure that could happen during
|
|
166
|
+
* resolution — an unset `HOME` — surfaces on {@link Xdg.layer} as an
|
|
167
|
+
* `XdgEnvError`, before an `AppDirs` exists at all. v3 laundered it into an
|
|
168
|
+
* `AppDirsError({ directory: "all" })`.
|
|
169
|
+
*/
|
|
170
|
+
static layer(options) {
|
|
171
|
+
return Layer.effect(AppDirs, Effect.gen(function* () {
|
|
172
|
+
const invalid = badNamespace(options.namespace);
|
|
173
|
+
if (invalid !== void 0) return yield* Effect.die(invalid);
|
|
174
|
+
const paths = yield* Xdg;
|
|
175
|
+
const fs = yield* FileSystem.FileSystem;
|
|
176
|
+
const path = yield* Path.Path;
|
|
177
|
+
const platform = yield* CurrentPlatform;
|
|
178
|
+
const dirs = resolveAll(options, paths, platform, path);
|
|
179
|
+
const makeDir = (kind, target) => fs.makeDirectory(target, { recursive: true }).pipe(Effect.mapError((cause) => new AppDirsError({
|
|
180
|
+
directory: kind,
|
|
181
|
+
path: target,
|
|
182
|
+
cause
|
|
183
|
+
})), Effect.as(target));
|
|
184
|
+
const runtimeDir = dirs.runtime;
|
|
185
|
+
const ensureRuntime = (runtimeDir === void 0 ? Effect.succeed(Option.none()) : Effect.map(makeDir("runtime", runtimeDir), Option.some)).pipe(Effect.withSpan("AppDirs.ensureRuntime"));
|
|
186
|
+
return {
|
|
187
|
+
namespace: options.namespace,
|
|
188
|
+
dirs,
|
|
189
|
+
ensureConfig: makeDir("config", dirs.config).pipe(Effect.withSpan("AppDirs.ensureConfig")),
|
|
190
|
+
ensureData: makeDir("data", dirs.data).pipe(Effect.withSpan("AppDirs.ensureData")),
|
|
191
|
+
ensureCache: makeDir("cache", dirs.cache).pipe(Effect.withSpan("AppDirs.ensureCache")),
|
|
192
|
+
ensureState: makeDir("state", dirs.state).pipe(Effect.withSpan("AppDirs.ensureState")),
|
|
193
|
+
ensureRuntime,
|
|
194
|
+
ensure: Effect.gen(function* () {
|
|
195
|
+
yield* makeDir("config", dirs.config);
|
|
196
|
+
yield* makeDir("data", dirs.data);
|
|
197
|
+
yield* makeDir("cache", dirs.cache);
|
|
198
|
+
yield* makeDir("state", dirs.state);
|
|
199
|
+
if (dirs.runtime !== void 0) yield* makeDir("runtime", dirs.runtime);
|
|
200
|
+
return dirs;
|
|
201
|
+
}).pipe(Effect.withSpan("AppDirs.ensure"))
|
|
202
|
+
};
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
//#endregion
|
|
208
|
+
export { AppDirKind, AppDirs, AppDirsError, ResolvedAppDirs };
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C. Spencer Beggs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NativeDirs.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/NativeDirs.ts
|
|
4
|
+
/**
|
|
5
|
+
* The OS-native application directories for one namespace.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* On platforms without OS-level separation of these concerns, `config`, `data`
|
|
9
|
+
* and `state` collapse to the same directory — `~/Library/Application Support/<ns>`
|
|
10
|
+
* on macOS — while `cache` stays distinct.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
var NativeDirs = class NativeDirs extends Schema.Class("NativeDirs")({
|
|
15
|
+
/** Where configuration lives. */
|
|
16
|
+
config: Schema.String,
|
|
17
|
+
/** Where application data lives. */
|
|
18
|
+
data: Schema.String,
|
|
19
|
+
/** Where discardable cached data lives. */
|
|
20
|
+
cache: Schema.String,
|
|
21
|
+
/** Where persistent-but-regenerable state lives. */
|
|
22
|
+
state: Schema.String
|
|
23
|
+
}) {
|
|
24
|
+
/**
|
|
25
|
+
* Map a platform and an environment onto the native directories for a namespace.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* **Pure**: no filesystem, no environment, no `process.platform`. Every input
|
|
29
|
+
* is a parameter, which is what makes the whole platform matrix testable
|
|
30
|
+
* without any platform IO. Paths are joined through the supplied `Path`, so a
|
|
31
|
+
* win32 `Path` layer yields win32 separators — v3 interpolated `/` on every
|
|
32
|
+
* platform.
|
|
33
|
+
*
|
|
34
|
+
* - **darwin** — `config`/`data`/`state` under `~/Library/Application Support/<ns>`;
|
|
35
|
+
* `cache` under `~/Library/Caches/<ns>`.
|
|
36
|
+
* - **win32** — `config`/`data` under `%APPDATA%/<ns>`; `cache` under
|
|
37
|
+
* `%LOCALAPPDATA%/<ns>/Cache`; `state` under `%LOCALAPPDATA%/<ns>`. When the
|
|
38
|
+
* variables are unset, `%APPDATA%` falls back to `<home>/AppData/Roaming` and
|
|
39
|
+
* `%LOCALAPPDATA%` to `<home>/AppData/Local`.
|
|
40
|
+
* - **everything else** — `Option.none()`. On Linux, XDG *is* the native
|
|
41
|
+
* convention, so there is no override to apply; returning `none` rather than
|
|
42
|
+
* a duplicate of the XDG answer is what lets a precedence ladder skip the
|
|
43
|
+
* rung cleanly instead of shadowing the rung below it.
|
|
44
|
+
*/
|
|
45
|
+
static resolve(input) {
|
|
46
|
+
const { platform, namespace, paths, path } = input;
|
|
47
|
+
if (platform === "darwin") {
|
|
48
|
+
const appSupport = path.join(paths.home, "Library", "Application Support", namespace);
|
|
49
|
+
return Option.some(NativeDirs.make({
|
|
50
|
+
config: appSupport,
|
|
51
|
+
data: appSupport,
|
|
52
|
+
cache: path.join(paths.home, "Library", "Caches", namespace),
|
|
53
|
+
state: appSupport
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
if (platform === "win32") {
|
|
57
|
+
const roaming = paths.appData ?? path.join(paths.home, "AppData", "Roaming");
|
|
58
|
+
const local = paths.localAppData ?? path.join(paths.home, "AppData", "Local");
|
|
59
|
+
return Option.some(NativeDirs.make({
|
|
60
|
+
config: path.join(roaming, namespace),
|
|
61
|
+
data: path.join(roaming, namespace),
|
|
62
|
+
cache: path.join(local, namespace, "Cache"),
|
|
63
|
+
state: path.join(local, namespace)
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
return Option.none();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { NativeDirs };
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# @effected/xdg
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@effected/xdg)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
XDG Base Directory resolution for Effect. `Xdg` reads the environment — `$HOME`, the four `*_HOME` variables, `$XDG_RUNTIME_DIR`, and the `$XDG_CONFIG_DIRS` / `$XDG_DATA_DIRS` search paths — once, at layer construction. `AppDirs` turns that into the config, data, cache, state and runtime directories for one application namespace, with on-demand creation. `NativeDirs` supplies the macOS and Windows conventions for applications that want them, and `XdgConfig` plugs the whole thing into [`@effected/config-file`](../config-file) as a resolver chain and a save target.
|
|
9
|
+
|
|
10
|
+
> **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
|
|
11
|
+
> development against a single pinned Effect v4 beta. Packages graduate to
|
|
12
|
+
> `1.0.0` once Effect `4.0.0` ships. To hold your own `effect` versions at
|
|
13
|
+
> exactly the ones the kit is built and tested against, install
|
|
14
|
+
> [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
|
|
15
|
+
>
|
|
16
|
+
> **Stability: unstable.** This package's API surface is not yet considered
|
|
17
|
+
> complete and may change across `0.x` releases. Pin an exact version — even a
|
|
18
|
+
> package marked *stable* before `1.0.0` can introduce a breaking change by
|
|
19
|
+
> accident, and an exact pin turns that into a type-check error rather than a
|
|
20
|
+
> runtime surprise. Full policy: [release strategy](https://github.com/spencerbeggs/effected#release-strategy).
|
|
21
|
+
|
|
22
|
+
## Why @effected/xdg
|
|
23
|
+
|
|
24
|
+
Path resolution is not IO, and modeling it as IO poisons everything downstream. The environment is fixed for the life of a process, so this package reads it exactly once — when the layer is built — and the service's shape *is* the resolved value. `appDirs.dirs.config` is a `string`, not an `Effect<string, XdgEnvError>`. Reading a path cannot fail, cannot be observed to do IO and drops straight into config-file's `defaultPath` slot, which is typed `Effect<string, never, R>` and would otherwise need an `orDie` to satisfy.
|
|
25
|
+
|
|
26
|
+
Two more things follow from taking the spec seriously. The system search paths are half of XDG and are usually skipped: a config lookup here probes the app's own config directory — whichever rung of the precedence below resolved it — *and then* each `$XDG_CONFIG_DIRS` entry, namespaced, and it absorbs failure per candidate — an unreadable `/etc/xdg` means "this candidate did not match", never "abort the search and hide the perfectly readable file below it". And the runtime directory has no fallback ladder, because there is no defensible one: it must be user-owned and mode 0700, so when `$XDG_RUNTIME_DIR` is unset the key is simply absent rather than pointing somewhere invented.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @effected/xdg effect @effect/platform-node
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm add @effected/xdg effect @effect/platform-node
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires Node.js >=24.11.0.
|
|
39
|
+
|
|
40
|
+
`effect` v4 is a peer dependency, and so are `@effected/walker` (the upward-traversal and search primitives the resolvers are built on) and `@effected/config-file` (whose `ConfigResolver` seam `XdgConfig` implements). Package managers that install peers automatically will pull them in; add them to your manifest explicitly if yours does not. There are no runtime dependencies.
|
|
41
|
+
|
|
42
|
+
Creating directories needs a `FileSystem` and a `Path` implementation, provided once at the edge — from `@effect/platform-node` on Node. Resolution itself needs neither.
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
Build `AppDirs` for your namespace, provide it the `Xdg` environment and the platform layers, and read the paths:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { AppDirs, Xdg } from "@effected/xdg";
|
|
50
|
+
import { NodeFileSystem, NodePath } from "@effect/platform-node";
|
|
51
|
+
import { Effect, Layer } from "effect";
|
|
52
|
+
|
|
53
|
+
const PlatformLive = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
|
|
54
|
+
|
|
55
|
+
const AppDirsLive = AppDirs.layer({ namespace: "myapp", native: true }).pipe(
|
|
56
|
+
Layer.provide(Layer.mergeAll(Xdg.layer, PlatformLive)),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const program = Effect.gen(function* () {
|
|
60
|
+
const appDirs = yield* AppDirs;
|
|
61
|
+
console.log(appDirs.dirs.config);
|
|
62
|
+
// Linux, XDG_CONFIG_HOME set: $XDG_CONFIG_HOME/myapp
|
|
63
|
+
// Linux, XDG_CONFIG_HOME unset: $HOME/.myapp
|
|
64
|
+
// macOS with `native: true`: $HOME/Library/Application Support/myapp
|
|
65
|
+
return yield* appDirs.ensureConfig;
|
|
66
|
+
// The same path, now created on disk.
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
Effect.runPromise(program.pipe(Effect.provide(AppDirsLive)));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`AppDirs.layer` is a layer-returning *function*, not a layer: calling it twice builds two independent services. Bind its result to a const, as above, and provide that const.
|
|
73
|
+
|
|
74
|
+
## Precedence
|
|
75
|
+
|
|
76
|
+
Each of `config`, `data`, `cache` and `state` resolves through five rungs, first match wins:
|
|
77
|
+
|
|
78
|
+
1. an explicit `dirs.<kind>` override — an absolute path, and it wins outright;
|
|
79
|
+
2. the XDG variable, namespaced: `$XDG_CONFIG_HOME/<namespace>`;
|
|
80
|
+
3. the OS-native directory, when `native: true` and the platform has one;
|
|
81
|
+
4. `$HOME/<fallbackDir>` — all four kinds collapse into that one directory;
|
|
82
|
+
5. `$HOME/.<namespace>`.
|
|
83
|
+
|
|
84
|
+
Rungs 4 and 5 are deliberately not the spec's per-kind defaults (`~/.config`, `~/.local/share`): an application that wants those passes them as `dirs` overrides. The runtime directory skips the ladder entirely — an override, or `$XDG_RUNTIME_DIR/<namespace>`, or nothing.
|
|
85
|
+
|
|
86
|
+
`NativeDirs.resolve` is pure — platform, namespace, environment and `Path` all arrive as parameters — so the whole matrix is testable with no filesystem and no `process.platform` read. On macOS `config`, `data` and `state` collapse to `~/Library/Application Support/<ns>` while `cache` stays under `~/Library/Caches/<ns>`; on Windows they split across `%APPDATA%` and `%LOCALAPPDATA%`. Everywhere else it returns `Option.none()`, because on Linux XDG *is* the native convention and there is nothing to override.
|
|
87
|
+
|
|
88
|
+
The platform is a `Context.Reference`, never a global read. Pin it in a test with `Layer.succeed(CurrentPlatform, "win32")` and exercise the Windows paths on a Mac.
|
|
89
|
+
|
|
90
|
+
## Config files
|
|
91
|
+
|
|
92
|
+
`XdgConfig` is the bridge into `@effected/config-file`. `resolver` searches the app's whole config search path, `nativeResolver` probes the OS-native directory, and `savePath` names the default write target:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { ConfigFile, JsonCodec, MergeStrategy } from "@effected/config-file";
|
|
96
|
+
import { XdgConfig } from "@effected/xdg";
|
|
97
|
+
import { Schema } from "effect";
|
|
98
|
+
|
|
99
|
+
class AppShape extends Schema.Class<AppShape>("AppShape")({
|
|
100
|
+
port: Schema.Number,
|
|
101
|
+
host: Schema.String,
|
|
102
|
+
}) {}
|
|
103
|
+
|
|
104
|
+
class AppConfig extends ConfigFile.Service<AppConfig, AppShape>()("myapp/Config") {}
|
|
105
|
+
|
|
106
|
+
export const AppConfigLive = ConfigFile.layer(AppConfig, {
|
|
107
|
+
schema: AppShape,
|
|
108
|
+
codec: JsonCodec,
|
|
109
|
+
resolvers: [
|
|
110
|
+
XdgConfig.resolver({ filename: "config.json" }),
|
|
111
|
+
XdgConfig.nativeResolver({ namespace: "myapp", filename: "config.json" }),
|
|
112
|
+
],
|
|
113
|
+
strategy: MergeStrategy.firstMatch<AppShape>(),
|
|
114
|
+
defaultPath: XdgConfig.savePath("config.json"),
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Order matters: put `resolver` before `nativeResolver` so an existing `~/.config/<app>/config.json` still wins over the OS-native directory. `savePath` does not create the directory — `ConfigFile.save` already creates the parent of whatever path it is handed.
|
|
119
|
+
|
|
120
|
+
## Errors
|
|
121
|
+
|
|
122
|
+
| Tag | Means | Recovery |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| `XdgEnvError` | `$HOME` is not set. Carries `variable` and the structural `cause` — the underlying `ConfigError`. | The one environment failure there is. Every other XDG variable is optional by construction, and its absence is a resolved default rather than an error. |
|
|
125
|
+
| `AppDirsError` | A directory could not be created. Carries `directory` (which kind), `path` and the structural `cause`. | The only way `AppDirs` fails, because resolution already happened. Check permissions. |
|
|
126
|
+
|
|
127
|
+
A namespace that is empty, or contains a path separator, or is exactly `.` or `..`, is a **defect** at layer construction rather than a typed error. It can only come from code, and `namespace: "../.."` would resolve the application's directories outside `$HOME` entirely.
|
|
128
|
+
|
|
129
|
+
## Features
|
|
130
|
+
|
|
131
|
+
- `Xdg` / `XdgPaths` — the resolved XDG environment as a value, including the `$XDG_CONFIG_DIRS` and `$XDG_DATA_DIRS` search paths, split and defaulted per the spec. `Xdg.layerFrom` serves fixed paths for tests.
|
|
132
|
+
- `AppDirs` / `ResolvedAppDirs` — the five app-namespaced directories plus the config and data search paths, with `ensureConfig`, `ensureData`, `ensureCache`, `ensureState`, `ensureRuntime` and `ensure` for on-demand creation.
|
|
133
|
+
- `NativeDirs` — the macOS and Windows conventions, resolved purely from a platform, a namespace and an environment.
|
|
134
|
+
- `CurrentPlatform` — the platform as a `Context.Reference`, defaulting to `process.platform` and overridable in a test.
|
|
135
|
+
- `XdgConfig` — `resolver`, `nativeResolver` and `savePath`, dropping straight into `@effected/config-file`'s `ConfigResolver` and `defaultPath` slots.
|
|
136
|
+
- `XdgEnvError` / `AppDirsError` — tagged errors carrying their cause structurally.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
[MIT](LICENSE)
|
package/Xdg.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Config, Context, Effect, Layer, Option, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/Xdg.ts
|
|
4
|
+
/**
|
|
5
|
+
* The operating system the path decisions are taken against.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* The members are Node's `process.platform` values, modeled as a schema rather
|
|
9
|
+
* than borrowed from the ambient `NodeJS.Platform` type so the package's public
|
|
10
|
+
* surface names nothing it does not own.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
const XdgPlatform = Schema.Literals([
|
|
15
|
+
"aix",
|
|
16
|
+
"android",
|
|
17
|
+
"darwin",
|
|
18
|
+
"freebsd",
|
|
19
|
+
"haiku",
|
|
20
|
+
"linux",
|
|
21
|
+
"openbsd",
|
|
22
|
+
"sunos",
|
|
23
|
+
"win32",
|
|
24
|
+
"cygwin",
|
|
25
|
+
"netbsd"
|
|
26
|
+
]);
|
|
27
|
+
const detectPlatform = () => {
|
|
28
|
+
const platform = globalThis.process?.platform;
|
|
29
|
+
return Schema.is(XdgPlatform)(platform) ? platform : "linux";
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The platform every native-directory decision is taken against.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* A {@link https://effect.website | Context.Reference}, not a global read. It
|
|
36
|
+
* defaults to `process.platform`, so production behaviour is what you expect;
|
|
37
|
+
* a test pins macOS or Windows semantics with
|
|
38
|
+
* `Layer.succeed(CurrentPlatform, "win32")` and exercises the whole native-path
|
|
39
|
+
* matrix without touching a real filesystem or the real platform.
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
const CurrentPlatform = Context.Reference("@effected/xdg/CurrentPlatform", { defaultValue: detectPlatform });
|
|
44
|
+
/**
|
|
45
|
+
* Indicates that the environment cannot satisfy XDG directory resolution.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* Raised only for `HOME`: every other XDG variable is optional by construction,
|
|
49
|
+
* and its absence is a resolved default rather than a failure. `cause` carries
|
|
50
|
+
* the underlying `ConfigError` structurally — v3 flattened it to a string.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
var XdgEnvError = class extends Schema.TaggedErrorClass()("XdgEnvError", {
|
|
55
|
+
/** The environment variable that was required and not found. */
|
|
56
|
+
variable: Schema.String,
|
|
57
|
+
/** The underlying failure, preserved structurally. */
|
|
58
|
+
cause: Schema.Defect()
|
|
59
|
+
}) {
|
|
60
|
+
get message() {
|
|
61
|
+
return `The ${this.variable} environment variable is not set`;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The XDG Base Directory environment, resolved.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* Every field but `home` is optional because the corresponding variable is:
|
|
69
|
+
* `Schema.optionalKey`, so an unset variable is an **absent key** and the read
|
|
70
|
+
* is `paths.configHome ?? fallback`. v3 modeled these as `Option`, which forced
|
|
71
|
+
* `Option.some(...)` into the construction API of everything downstream.
|
|
72
|
+
*
|
|
73
|
+
* `configDirs` and `dataDirs` are the colon-separated system search paths, split
|
|
74
|
+
* and defaulted per the spec (`/etc/xdg` and `/usr/local/share:/usr/share`).
|
|
75
|
+
* They are what make a config lookup a genuine ordered search rather than a
|
|
76
|
+
* single stat.
|
|
77
|
+
*
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
var XdgPaths = class extends Schema.Class("XdgPaths")({
|
|
81
|
+
/** `$HOME`. The one variable that must be set. */
|
|
82
|
+
home: Schema.String,
|
|
83
|
+
/** `$XDG_CONFIG_HOME`. */
|
|
84
|
+
configHome: Schema.optionalKey(Schema.String),
|
|
85
|
+
/** `$XDG_DATA_HOME`. */
|
|
86
|
+
dataHome: Schema.optionalKey(Schema.String),
|
|
87
|
+
/** `$XDG_CACHE_HOME`. */
|
|
88
|
+
cacheHome: Schema.optionalKey(Schema.String),
|
|
89
|
+
/** `$XDG_STATE_HOME`. */
|
|
90
|
+
stateHome: Schema.optionalKey(Schema.String),
|
|
91
|
+
/** `$XDG_RUNTIME_DIR`. Absent on most non-Linux systems. */
|
|
92
|
+
runtimeDir: Schema.optionalKey(Schema.String),
|
|
93
|
+
/** `%APPDATA%`, on Windows. */
|
|
94
|
+
appData: Schema.optionalKey(Schema.String),
|
|
95
|
+
/** `%LOCALAPPDATA%`, on Windows. */
|
|
96
|
+
localAppData: Schema.optionalKey(Schema.String),
|
|
97
|
+
/** `$XDG_CONFIG_DIRS`, split on `:`. Defaults to `["/etc/xdg"]`. */
|
|
98
|
+
configDirs: Schema.Array(Schema.String),
|
|
99
|
+
/** `$XDG_DATA_DIRS`, split on `:`. Defaults to `["/usr/local/share", "/usr/share"]`. */
|
|
100
|
+
dataDirs: Schema.Array(Schema.String)
|
|
101
|
+
}) {};
|
|
102
|
+
/**
|
|
103
|
+
* Split a `PATH`-style variable, dropping empty entries.
|
|
104
|
+
*
|
|
105
|
+
* @remarks
|
|
106
|
+
* Per the XDG spec, an unset **or empty** variable takes the default — so
|
|
107
|
+
* `XDG_CONFIG_DIRS=""` is not "no system directories", it is `/etc/xdg`.
|
|
108
|
+
*/
|
|
109
|
+
const splitDirs = (raw, fallback) => {
|
|
110
|
+
if (raw === void 0) return fallback;
|
|
111
|
+
const parts = raw.split(":").filter((entry) => entry.length > 0);
|
|
112
|
+
return parts.length === 0 ? fallback : parts;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* XDG Base Directory environment resolution.
|
|
116
|
+
*
|
|
117
|
+
* @remarks
|
|
118
|
+
* The service's shape **is** {@link XdgPaths}: the environment is read once, at
|
|
119
|
+
* layer construction, and the service is the resolved value. v3 exposed nine
|
|
120
|
+
* `Effect`s that each re-read the environment on every access, which is why
|
|
121
|
+
* every path downstream of it was fallible. Here `yield* Xdg` gives you a record
|
|
122
|
+
* of strings, and the only failure — an unset `HOME` — happens once, where the
|
|
123
|
+
* layer is built.
|
|
124
|
+
*
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
var Xdg = class Xdg extends Context.Service()("@effected/xdg/Xdg") {
|
|
128
|
+
/**
|
|
129
|
+
* Read the XDG environment through Effect's `Config`.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* Reads from the ambient `ConfigProvider`, which defaults to `process.env`.
|
|
133
|
+
* A test drives it with `ConfigProvider.layer(ConfigProvider.fromUnknown({…}))`
|
|
134
|
+
* and never mutates the real environment.
|
|
135
|
+
*/
|
|
136
|
+
static layer = Layer.effect(Xdg, Effect.gen(function* () {
|
|
137
|
+
/**
|
|
138
|
+
* `Config<T>` IS an `Effect<T, ConfigError>` in v4, so it pipes directly.
|
|
139
|
+
* Every `ConfigError` becomes an `XdgEnvError` naming the variable it came from.
|
|
140
|
+
*/
|
|
141
|
+
const asEnvError = (name, config) => Effect.catchTag(config, "ConfigError", (cause) => Effect.fail(new XdgEnvError({
|
|
142
|
+
variable: name,
|
|
143
|
+
cause
|
|
144
|
+
})));
|
|
145
|
+
const home = yield* asEnvError("HOME", Config.string("HOME"));
|
|
146
|
+
/**
|
|
147
|
+
* An unset variable is `Option.none()`, not a failure. The residual
|
|
148
|
+
* `ConfigError` a `Config.option` can still raise is a *provider* failure
|
|
149
|
+
* (a `ConfigProvider` backed by a directory or a `.env` file that cannot be
|
|
150
|
+
* read), not a missing key — so it is mapped rather than swallowed.
|
|
151
|
+
*/
|
|
152
|
+
const read = (name) => Effect.map(asEnvError(name, Config.option(Config.string(name))), Option.getOrUndefined);
|
|
153
|
+
const configHome = yield* read("XDG_CONFIG_HOME");
|
|
154
|
+
const dataHome = yield* read("XDG_DATA_HOME");
|
|
155
|
+
const cacheHome = yield* read("XDG_CACHE_HOME");
|
|
156
|
+
const stateHome = yield* read("XDG_STATE_HOME");
|
|
157
|
+
const runtimeDir = yield* read("XDG_RUNTIME_DIR");
|
|
158
|
+
const appData = yield* read("APPDATA");
|
|
159
|
+
const localAppData = yield* read("LOCALAPPDATA");
|
|
160
|
+
const configDirs = yield* read("XDG_CONFIG_DIRS");
|
|
161
|
+
const dataDirs = yield* read("XDG_DATA_DIRS");
|
|
162
|
+
return XdgPaths.make({
|
|
163
|
+
home,
|
|
164
|
+
...configHome !== void 0 && { configHome },
|
|
165
|
+
...dataHome !== void 0 && { dataHome },
|
|
166
|
+
...cacheHome !== void 0 && { cacheHome },
|
|
167
|
+
...stateHome !== void 0 && { stateHome },
|
|
168
|
+
...runtimeDir !== void 0 && { runtimeDir },
|
|
169
|
+
...appData !== void 0 && { appData },
|
|
170
|
+
...localAppData !== void 0 && { localAppData },
|
|
171
|
+
configDirs: splitDirs(configDirs, ["/etc/xdg"]),
|
|
172
|
+
dataDirs: splitDirs(dataDirs, ["/usr/local/share", "/usr/share"])
|
|
173
|
+
});
|
|
174
|
+
}));
|
|
175
|
+
/**
|
|
176
|
+
* Serve fixed paths instead of reading the environment.
|
|
177
|
+
*
|
|
178
|
+
* @remarks
|
|
179
|
+
* The test layer, and the escape hatch for an application that resolves its
|
|
180
|
+
* environment some other way. It needs no filesystem — v3's test layer reached
|
|
181
|
+
* past the platform abstraction for a `node:fs` temp directory.
|
|
182
|
+
*/
|
|
183
|
+
static layerFrom(paths) {
|
|
184
|
+
return Layer.succeed(Xdg, paths);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
export { CurrentPlatform, Xdg, XdgEnvError, XdgPaths, XdgPlatform };
|
package/XdgConfig.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { NativeDirs } from "./NativeDirs.js";
|
|
2
|
+
import { CurrentPlatform, Xdg } from "./Xdg.js";
|
|
3
|
+
import { AppDirs } from "./AppDirs.js";
|
|
4
|
+
import { Effect, FileSystem, Option, Path } from "effect";
|
|
5
|
+
import { Walker } from "@effected/walker";
|
|
6
|
+
|
|
7
|
+
//#region src/XdgConfig.ts
|
|
8
|
+
/**
|
|
9
|
+
* Search the app's XDG config search path for `filename`.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* Probes the app's own config directory first, then each `$XDG_CONFIG_DIRS`
|
|
13
|
+
* entry namespaced — `~/.config/myapp/rc`, then `/etc/xdg/myapp/rc`. v3 probed
|
|
14
|
+
* only the first of those; the system search path is half the XDG spec and it
|
|
15
|
+
* was missing.
|
|
16
|
+
*
|
|
17
|
+
* The scan runs through `Walker.firstMatch`, so a failure on one candidate means
|
|
18
|
+
* "this candidate did not match" and the search continues to the next. That is a
|
|
19
|
+
* bug fixed, not a refactor: v3 wrapped the whole resolver in a single
|
|
20
|
+
* `catchAll`, so an unreadable `/etc/xdg` aborted the probe and hid a perfectly
|
|
21
|
+
* readable `~/.config`. Not-found and cannot-look stay indistinguishable to the
|
|
22
|
+
* caller, which is the resolver contract — `resolve`'s error channel is `never`.
|
|
23
|
+
*
|
|
24
|
+
* Place it **before** `nativeResolver` in a chain, so an existing
|
|
25
|
+
* `~/.config/<app>` still wins over the OS-native directory.
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
const resolver = (options) => ({
|
|
30
|
+
name: "xdg",
|
|
31
|
+
resolve: Effect.gen(function* () {
|
|
32
|
+
const appDirs = yield* AppDirs;
|
|
33
|
+
const path = yield* Path.Path;
|
|
34
|
+
const fs = yield* FileSystem.FileSystem;
|
|
35
|
+
const candidates = appDirs.dirs.configSearchPath.map((dir) => path.join(dir, options.filename));
|
|
36
|
+
return yield* Walker.firstMatch(candidates, (candidate) => fs.exists(candidate));
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Probe the OS-native config directory for `filename`.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* Resolves the native config directory for `namespace`
|
|
44
|
+
* (`~/Library/Application Support/<ns>` on macOS, `%APPDATA%\<ns>` on Windows)
|
|
45
|
+
* and checks whether `filename` is there. On Linux and everywhere else
|
|
46
|
+
* {@link NativeDirs.resolve} yields `Option.none()`, so this resolver returns
|
|
47
|
+
* `Option.none()` without probing at all — the XDG resolver already owns
|
|
48
|
+
* `~/.config` there.
|
|
49
|
+
*
|
|
50
|
+
* Takes `namespace` rather than reading it off {@link AppDirs}: the native
|
|
51
|
+
* directory is a property of the OS convention, not of however the app happened
|
|
52
|
+
* to configure its XDG directories, and a caller may well probe a *different*
|
|
53
|
+
* namespace than the one their `AppDirs` was built for.
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
const nativeResolver = (options) => ({
|
|
58
|
+
name: "native",
|
|
59
|
+
resolve: Effect.gen(function* () {
|
|
60
|
+
const paths = yield* Xdg;
|
|
61
|
+
const platform = yield* CurrentPlatform;
|
|
62
|
+
const path = yield* Path.Path;
|
|
63
|
+
const fs = yield* FileSystem.FileSystem;
|
|
64
|
+
const native = NativeDirs.resolve({
|
|
65
|
+
platform,
|
|
66
|
+
namespace: options.namespace,
|
|
67
|
+
paths,
|
|
68
|
+
path
|
|
69
|
+
});
|
|
70
|
+
if (Option.isNone(native)) return Option.none();
|
|
71
|
+
return yield* Walker.firstMatch([path.join(native.value.config, options.filename)], (candidate) => fs.exists(candidate));
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
/**
|
|
75
|
+
* The default save target for a config file: `<app config dir>/<filename>`.
|
|
76
|
+
*
|
|
77
|
+
* @remarks
|
|
78
|
+
* Drops straight into `ConfigFileOptions.defaultPath`, whose slot is typed
|
|
79
|
+
* `Effect<string, never, RR>`. That infallible channel is the whole reason
|
|
80
|
+
* {@link AppDirs} resolves at layer-construction time: with v3's per-access
|
|
81
|
+
* resolution this could fail, and a consumer had to `orDie` it into the slot.
|
|
82
|
+
*
|
|
83
|
+
* It does **not** create the directory — `ConfigFile.save` already `mkdir -p`s
|
|
84
|
+
* the parent of whatever path it is given.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const layer = ConfigFile.layer(AppConfig, {
|
|
89
|
+
* schema: AppShape,
|
|
90
|
+
* codec: JsonCodec,
|
|
91
|
+
* strategy: MergeStrategy.firstMatch<AppShape>(),
|
|
92
|
+
* resolvers: [XdgConfig.resolver({ filename: "config.json" })],
|
|
93
|
+
* defaultPath: XdgConfig.savePath("config.json"),
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @public
|
|
98
|
+
*/
|
|
99
|
+
const savePath = (filename) => Effect.gen(function* () {
|
|
100
|
+
const appDirs = yield* AppDirs;
|
|
101
|
+
return (yield* Path.Path).join(appDirs.dirs.config, filename);
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* The bridge from XDG directories into `@effected/config-file`.
|
|
105
|
+
*
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
108
|
+
const XdgConfig = {
|
|
109
|
+
resolver,
|
|
110
|
+
nativeResolver,
|
|
111
|
+
savePath
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
export { XdgConfig };
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { Context, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
|
|
2
|
+
import { ConfigResolver } from "@effected/config-file";
|
|
3
|
+
//#region src/Xdg.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The operating system the path decisions are taken against.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* The members are Node's `process.platform` values, modeled as a schema rather
|
|
9
|
+
* than borrowed from the ambient `NodeJS.Platform` type so the package's public
|
|
10
|
+
* surface names nothing it does not own.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
declare const XdgPlatform: Schema.Literals<readonly ["aix", "android", "darwin", "freebsd", "haiku", "linux", "openbsd", "sunos", "win32", "cygwin", "netbsd"]>;
|
|
15
|
+
/**
|
|
16
|
+
* The decoded form of {@link (XdgPlatform:variable)}.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
type XdgPlatform = typeof XdgPlatform.Type;
|
|
21
|
+
/**
|
|
22
|
+
* The platform every native-directory decision is taken against.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* A {@link https://effect.website | Context.Reference}, not a global read. It
|
|
26
|
+
* defaults to `process.platform`, so production behaviour is what you expect;
|
|
27
|
+
* a test pins macOS or Windows semantics with
|
|
28
|
+
* `Layer.succeed(CurrentPlatform, "win32")` and exercises the whole native-path
|
|
29
|
+
* matrix without touching a real filesystem or the real platform.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
declare const CurrentPlatform: Context.Reference<XdgPlatform>;
|
|
34
|
+
declare const XdgEnvError_base: Schema.Class<XdgEnvError, Schema.TaggedStruct<"XdgEnvError", {
|
|
35
|
+
/** The environment variable that was required and not found. */
|
|
36
|
+
readonly variable: Schema.String;
|
|
37
|
+
/** The underlying failure, preserved structurally. */
|
|
38
|
+
readonly cause: Schema.Defect;
|
|
39
|
+
}>, import("effect/Cause").YieldableError>;
|
|
40
|
+
/**
|
|
41
|
+
* Indicates that the environment cannot satisfy XDG directory resolution.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Raised only for `HOME`: every other XDG variable is optional by construction,
|
|
45
|
+
* and its absence is a resolved default rather than a failure. `cause` carries
|
|
46
|
+
* the underlying `ConfigError` structurally — v3 flattened it to a string.
|
|
47
|
+
*
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
declare class XdgEnvError extends XdgEnvError_base {
|
|
51
|
+
get message(): string;
|
|
52
|
+
}
|
|
53
|
+
declare const XdgPaths_base: Schema.Class<XdgPaths, Schema.Struct<{
|
|
54
|
+
/** `$HOME`. The one variable that must be set. */
|
|
55
|
+
readonly home: Schema.String;
|
|
56
|
+
/** `$XDG_CONFIG_HOME`. */
|
|
57
|
+
readonly configHome: Schema.optionalKey<Schema.String>;
|
|
58
|
+
/** `$XDG_DATA_HOME`. */
|
|
59
|
+
readonly dataHome: Schema.optionalKey<Schema.String>;
|
|
60
|
+
/** `$XDG_CACHE_HOME`. */
|
|
61
|
+
readonly cacheHome: Schema.optionalKey<Schema.String>;
|
|
62
|
+
/** `$XDG_STATE_HOME`. */
|
|
63
|
+
readonly stateHome: Schema.optionalKey<Schema.String>;
|
|
64
|
+
/** `$XDG_RUNTIME_DIR`. Absent on most non-Linux systems. */
|
|
65
|
+
readonly runtimeDir: Schema.optionalKey<Schema.String>;
|
|
66
|
+
/** `%APPDATA%`, on Windows. */
|
|
67
|
+
readonly appData: Schema.optionalKey<Schema.String>;
|
|
68
|
+
/** `%LOCALAPPDATA%`, on Windows. */
|
|
69
|
+
readonly localAppData: Schema.optionalKey<Schema.String>;
|
|
70
|
+
/** `$XDG_CONFIG_DIRS`, split on `:`. Defaults to `["/etc/xdg"]`. */
|
|
71
|
+
readonly configDirs: Schema.$Array<Schema.String>;
|
|
72
|
+
/** `$XDG_DATA_DIRS`, split on `:`. Defaults to `["/usr/local/share", "/usr/share"]`. */
|
|
73
|
+
readonly dataDirs: Schema.$Array<Schema.String>;
|
|
74
|
+
}>, {}>;
|
|
75
|
+
/**
|
|
76
|
+
* The XDG Base Directory environment, resolved.
|
|
77
|
+
*
|
|
78
|
+
* @remarks
|
|
79
|
+
* Every field but `home` is optional because the corresponding variable is:
|
|
80
|
+
* `Schema.optionalKey`, so an unset variable is an **absent key** and the read
|
|
81
|
+
* is `paths.configHome ?? fallback`. v3 modeled these as `Option`, which forced
|
|
82
|
+
* `Option.some(...)` into the construction API of everything downstream.
|
|
83
|
+
*
|
|
84
|
+
* `configDirs` and `dataDirs` are the colon-separated system search paths, split
|
|
85
|
+
* and defaulted per the spec (`/etc/xdg` and `/usr/local/share:/usr/share`).
|
|
86
|
+
* They are what make a config lookup a genuine ordered search rather than a
|
|
87
|
+
* single stat.
|
|
88
|
+
*
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
91
|
+
declare class XdgPaths extends XdgPaths_base {}
|
|
92
|
+
declare const Xdg_base: Context.ServiceClass<Xdg, "@effected/xdg/Xdg", XdgPaths>;
|
|
93
|
+
/**
|
|
94
|
+
* XDG Base Directory environment resolution.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* The service's shape **is** {@link XdgPaths}: the environment is read once, at
|
|
98
|
+
* layer construction, and the service is the resolved value. v3 exposed nine
|
|
99
|
+
* `Effect`s that each re-read the environment on every access, which is why
|
|
100
|
+
* every path downstream of it was fallible. Here `yield* Xdg` gives you a record
|
|
101
|
+
* of strings, and the only failure — an unset `HOME` — happens once, where the
|
|
102
|
+
* layer is built.
|
|
103
|
+
*
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
declare class Xdg extends Xdg_base {
|
|
107
|
+
/**
|
|
108
|
+
* Read the XDG environment through Effect's `Config`.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* Reads from the ambient `ConfigProvider`, which defaults to `process.env`.
|
|
112
|
+
* A test drives it with `ConfigProvider.layer(ConfigProvider.fromUnknown({…}))`
|
|
113
|
+
* and never mutates the real environment.
|
|
114
|
+
*/
|
|
115
|
+
static readonly layer: Layer.Layer<Xdg, XdgEnvError>;
|
|
116
|
+
/**
|
|
117
|
+
* Serve fixed paths instead of reading the environment.
|
|
118
|
+
*
|
|
119
|
+
* @remarks
|
|
120
|
+
* The test layer, and the escape hatch for an application that resolves its
|
|
121
|
+
* environment some other way. It needs no filesystem — v3's test layer reached
|
|
122
|
+
* past the platform abstraction for a `node:fs` temp directory.
|
|
123
|
+
*/
|
|
124
|
+
static layerFrom(paths: XdgPaths): Layer.Layer<Xdg>;
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/AppDirs.d.ts
|
|
128
|
+
/**
|
|
129
|
+
* The four directory kinds XDG separates, plus the runtime directory.
|
|
130
|
+
*
|
|
131
|
+
* @public
|
|
132
|
+
*/
|
|
133
|
+
declare const AppDirKind: Schema.Literals<readonly ["config", "data", "cache", "state", "runtime"]>;
|
|
134
|
+
/**
|
|
135
|
+
* The decoded form of {@link (AppDirKind:variable)}.
|
|
136
|
+
*
|
|
137
|
+
* @public
|
|
138
|
+
*/
|
|
139
|
+
type AppDirKind = typeof AppDirKind.Type;
|
|
140
|
+
declare const AppDirsError_base: Schema.Class<AppDirsError, Schema.TaggedStruct<"AppDirsError", {
|
|
141
|
+
/** Which directory kind failed. */
|
|
142
|
+
readonly directory: Schema.Literals<readonly ["config", "data", "cache", "state", "runtime"]>;
|
|
143
|
+
/** The path that could not be created. */
|
|
144
|
+
readonly path: Schema.String;
|
|
145
|
+
/** The underlying failure, preserved structurally. */
|
|
146
|
+
readonly cause: Schema.Defect;
|
|
147
|
+
}>, import("effect/Cause").YieldableError>;
|
|
148
|
+
/**
|
|
149
|
+
* Indicates that an application directory could not be created.
|
|
150
|
+
*
|
|
151
|
+
* @remarks
|
|
152
|
+
* The only way `AppDirs` fails. Resolution cannot fail — it happens once, at
|
|
153
|
+
* layer construction, from an environment that is already resolved — so this
|
|
154
|
+
* error means exactly one thing: the `mkdir -p` did not work. `directory` says
|
|
155
|
+
* which kind, `path` says where, and `cause` carries the underlying
|
|
156
|
+
* `PlatformError` structurally. v3 carried `reason: String(e)` and a `directory`
|
|
157
|
+
* that could also be the string `"all"`.
|
|
158
|
+
*
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
declare class AppDirsError extends AppDirsError_base {
|
|
162
|
+
get message(): string;
|
|
163
|
+
}
|
|
164
|
+
declare const ResolvedAppDirs_base: Schema.Class<ResolvedAppDirs, Schema.Struct<{
|
|
165
|
+
/** The app's configuration directory. */
|
|
166
|
+
readonly config: Schema.String;
|
|
167
|
+
/** The app's data directory. */
|
|
168
|
+
readonly data: Schema.String;
|
|
169
|
+
/** The app's cache directory. */
|
|
170
|
+
readonly cache: Schema.String;
|
|
171
|
+
/** The app's state directory. */
|
|
172
|
+
readonly state: Schema.String;
|
|
173
|
+
/**
|
|
174
|
+
* The app's runtime directory.
|
|
175
|
+
*
|
|
176
|
+
* @remarks
|
|
177
|
+
* Absent unless `$XDG_RUNTIME_DIR` is set or `dirs.runtime` overrides it.
|
|
178
|
+
* There is no defensible fallback for a runtime directory — it must be
|
|
179
|
+
* user-owned, mode 0700 and cleaned on logout — so inventing one would be a
|
|
180
|
+
* lie, and the key is simply absent.
|
|
181
|
+
*/
|
|
182
|
+
readonly runtime: Schema.optionalKey<Schema.String>;
|
|
183
|
+
/**
|
|
184
|
+
* Where to **look** for configuration, in priority order.
|
|
185
|
+
*
|
|
186
|
+
* @remarks
|
|
187
|
+
* The app's own config directory, then each `$XDG_CONFIG_DIRS` entry
|
|
188
|
+
* namespaced. This is the half of the XDG spec v3 ignored entirely, and it is
|
|
189
|
+
* what makes {@link XdgConfig.resolver} a real search rather than a single stat.
|
|
190
|
+
*/
|
|
191
|
+
readonly configSearchPath: Schema.$Array<Schema.String>;
|
|
192
|
+
/** Where to look for data files, in priority order. */
|
|
193
|
+
readonly dataSearchPath: Schema.$Array<Schema.String>;
|
|
194
|
+
}>, {}>;
|
|
195
|
+
/**
|
|
196
|
+
* The fully resolved, app-namespaced directories.
|
|
197
|
+
*
|
|
198
|
+
* @public
|
|
199
|
+
*/
|
|
200
|
+
declare class ResolvedAppDirs extends ResolvedAppDirs_base {}
|
|
201
|
+
/**
|
|
202
|
+
* Per-kind absolute directory overrides. Each wins outright over every other rung.
|
|
203
|
+
*
|
|
204
|
+
* @public
|
|
205
|
+
*/
|
|
206
|
+
interface AppDirOverrides {
|
|
207
|
+
/** An absolute path for the config directory. */
|
|
208
|
+
readonly config?: string;
|
|
209
|
+
/** An absolute path for the data directory. */
|
|
210
|
+
readonly data?: string;
|
|
211
|
+
/** An absolute path for the cache directory. */
|
|
212
|
+
readonly cache?: string;
|
|
213
|
+
/** An absolute path for the state directory. */
|
|
214
|
+
readonly state?: string;
|
|
215
|
+
/** An absolute path for the runtime directory. */
|
|
216
|
+
readonly runtime?: string;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Options for {@link AppDirs.layer}.
|
|
220
|
+
*
|
|
221
|
+
* @remarks
|
|
222
|
+
* Plain optional fields, not `Option`s: v3 made callers write
|
|
223
|
+
* `fallbackDir: Option.some(".myapp"), dirs: Option.none()`. `Option` is an
|
|
224
|
+
* internal representation, not an input format.
|
|
225
|
+
*
|
|
226
|
+
* @public
|
|
227
|
+
*/
|
|
228
|
+
interface AppDirsOptions {
|
|
229
|
+
/**
|
|
230
|
+
* The application namespace — one path component.
|
|
231
|
+
*
|
|
232
|
+
* @remarks
|
|
233
|
+
* Must be non-empty and free of path separators. A namespace containing `..`
|
|
234
|
+
* or `/` would resolve the app's directories outside `$HOME` entirely, so it
|
|
235
|
+
* is rejected as a **defect** at layer construction: it can only come from
|
|
236
|
+
* code, never from user input.
|
|
237
|
+
*/
|
|
238
|
+
readonly namespace: string;
|
|
239
|
+
/**
|
|
240
|
+
* Use the OS-native directories where the platform has them. Defaults to `false`.
|
|
241
|
+
*
|
|
242
|
+
* @remarks
|
|
243
|
+
* Only consulted when no XDG variable and no explicit override applies, and
|
|
244
|
+
* only on darwin and win32 — on Linux there is nothing to override.
|
|
245
|
+
*/
|
|
246
|
+
readonly native?: boolean;
|
|
247
|
+
/**
|
|
248
|
+
* A single dot-directory under `$HOME` that all four kinds collapse to.
|
|
249
|
+
*
|
|
250
|
+
* @remarks
|
|
251
|
+
* Relative to `$HOME`: `fallbackDir: ".myapp"` gives `$HOME/.myapp`.
|
|
252
|
+
*/
|
|
253
|
+
readonly fallbackDir?: string;
|
|
254
|
+
/** Absolute per-kind overrides. */
|
|
255
|
+
readonly dirs?: AppDirOverrides;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* App-namespaced directory resolution and on-demand creation.
|
|
259
|
+
*
|
|
260
|
+
* @remarks
|
|
261
|
+
* `dirs` is a **value**, not an `Effect`: the environment is fixed when the
|
|
262
|
+
* layer is built, so resolution happens there, exactly once. Reading a path
|
|
263
|
+
* cannot fail and cannot be observed to do IO. Only the `ensure*` operations
|
|
264
|
+
* touch the filesystem, and they are the only fallible members.
|
|
265
|
+
*
|
|
266
|
+
* @public
|
|
267
|
+
*/
|
|
268
|
+
interface AppDirsShape {
|
|
269
|
+
/** The namespace these directories were resolved for. */
|
|
270
|
+
readonly namespace: string;
|
|
271
|
+
/** The resolved directories. Reading them cannot fail. */
|
|
272
|
+
readonly dirs: ResolvedAppDirs;
|
|
273
|
+
/** Create the config directory if it does not exist, and return it. */
|
|
274
|
+
readonly ensureConfig: Effect.Effect<string, AppDirsError>;
|
|
275
|
+
/** Create the data directory if it does not exist, and return it. */
|
|
276
|
+
readonly ensureData: Effect.Effect<string, AppDirsError>;
|
|
277
|
+
/** Create the cache directory if it does not exist, and return it. */
|
|
278
|
+
readonly ensureCache: Effect.Effect<string, AppDirsError>;
|
|
279
|
+
/** Create the state directory if it does not exist, and return it. */
|
|
280
|
+
readonly ensureState: Effect.Effect<string, AppDirsError>;
|
|
281
|
+
/**
|
|
282
|
+
* Create the runtime directory if there is one, and return it.
|
|
283
|
+
*
|
|
284
|
+
* @remarks
|
|
285
|
+
* `Option.none()` when no runtime directory is configured — nothing is created
|
|
286
|
+
* and nothing fails.
|
|
287
|
+
*/
|
|
288
|
+
readonly ensureRuntime: Effect.Effect<Option.Option<string>, AppDirsError>;
|
|
289
|
+
/** Create every directory that exists in the resolution, and return them all. */
|
|
290
|
+
readonly ensure: Effect.Effect<ResolvedAppDirs, AppDirsError>;
|
|
291
|
+
}
|
|
292
|
+
declare const AppDirs_base: Context.ServiceClass<AppDirs, "@effected/xdg/AppDirs", AppDirsShape>;
|
|
293
|
+
/**
|
|
294
|
+
* App-namespaced XDG directories, with on-demand creation.
|
|
295
|
+
*
|
|
296
|
+
* @remarks
|
|
297
|
+
* `AppDirs.layer` is a layer-**returning function**: calling it twice builds two
|
|
298
|
+
* independent services. Bind its result to a const and provide that const, per
|
|
299
|
+
* the layer memoization discipline.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```ts
|
|
303
|
+
* const AppDirsLayer = AppDirs.layer({ namespace: "myapp", native: true });
|
|
304
|
+
* const XdgLayer = Layer.mergeAll(Xdg.layer, AppDirsLayer.pipe(Layer.provide(Xdg.layer)));
|
|
305
|
+
* ```
|
|
306
|
+
*
|
|
307
|
+
* @public
|
|
308
|
+
*/
|
|
309
|
+
declare class AppDirs extends AppDirs_base {
|
|
310
|
+
/**
|
|
311
|
+
* Resolve the namespace's directories against the ambient {@link Xdg}
|
|
312
|
+
* environment and platform.
|
|
313
|
+
*
|
|
314
|
+
* @remarks
|
|
315
|
+
* The error channel is `never`. The one failure that could happen during
|
|
316
|
+
* resolution — an unset `HOME` — surfaces on {@link Xdg.layer} as an
|
|
317
|
+
* `XdgEnvError`, before an `AppDirs` exists at all. v3 laundered it into an
|
|
318
|
+
* `AppDirsError({ directory: "all" })`.
|
|
319
|
+
*/
|
|
320
|
+
static layer(options: AppDirsOptions): Layer.Layer<AppDirs, never, Xdg | FileSystem.FileSystem | Path.Path>;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/NativeDirs.d.ts
|
|
324
|
+
declare const NativeDirs_base: Schema.Class<NativeDirs, Schema.Struct<{
|
|
325
|
+
/** Where configuration lives. */
|
|
326
|
+
readonly config: Schema.String;
|
|
327
|
+
/** Where application data lives. */
|
|
328
|
+
readonly data: Schema.String;
|
|
329
|
+
/** Where discardable cached data lives. */
|
|
330
|
+
readonly cache: Schema.String;
|
|
331
|
+
/** Where persistent-but-regenerable state lives. */
|
|
332
|
+
readonly state: Schema.String;
|
|
333
|
+
}>, {}>;
|
|
334
|
+
/**
|
|
335
|
+
* The OS-native application directories for one namespace.
|
|
336
|
+
*
|
|
337
|
+
* @remarks
|
|
338
|
+
* On platforms without OS-level separation of these concerns, `config`, `data`
|
|
339
|
+
* and `state` collapse to the same directory — `~/Library/Application Support/<ns>`
|
|
340
|
+
* on macOS — while `cache` stays distinct.
|
|
341
|
+
*
|
|
342
|
+
* @public
|
|
343
|
+
*/
|
|
344
|
+
declare class NativeDirs extends NativeDirs_base {
|
|
345
|
+
/**
|
|
346
|
+
* Map a platform and an environment onto the native directories for a namespace.
|
|
347
|
+
*
|
|
348
|
+
* @remarks
|
|
349
|
+
* **Pure**: no filesystem, no environment, no `process.platform`. Every input
|
|
350
|
+
* is a parameter, which is what makes the whole platform matrix testable
|
|
351
|
+
* without any platform IO. Paths are joined through the supplied `Path`, so a
|
|
352
|
+
* win32 `Path` layer yields win32 separators — v3 interpolated `/` on every
|
|
353
|
+
* platform.
|
|
354
|
+
*
|
|
355
|
+
* - **darwin** — `config`/`data`/`state` under `~/Library/Application Support/<ns>`;
|
|
356
|
+
* `cache` under `~/Library/Caches/<ns>`.
|
|
357
|
+
* - **win32** — `config`/`data` under `%APPDATA%/<ns>`; `cache` under
|
|
358
|
+
* `%LOCALAPPDATA%/<ns>/Cache`; `state` under `%LOCALAPPDATA%/<ns>`. When the
|
|
359
|
+
* variables are unset, `%APPDATA%` falls back to `<home>/AppData/Roaming` and
|
|
360
|
+
* `%LOCALAPPDATA%` to `<home>/AppData/Local`.
|
|
361
|
+
* - **everything else** — `Option.none()`. On Linux, XDG *is* the native
|
|
362
|
+
* convention, so there is no override to apply; returning `none` rather than
|
|
363
|
+
* a duplicate of the XDG answer is what lets a precedence ladder skip the
|
|
364
|
+
* rung cleanly instead of shadowing the rung below it.
|
|
365
|
+
*/
|
|
366
|
+
static resolve(input: {
|
|
367
|
+
readonly platform: XdgPlatform;
|
|
368
|
+
readonly namespace: string;
|
|
369
|
+
readonly paths: XdgPaths;
|
|
370
|
+
readonly path: Path.Path;
|
|
371
|
+
}): Option.Option<NativeDirs>;
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/XdgConfig.d.ts
|
|
375
|
+
/**
|
|
376
|
+
* The bridge from XDG directories into `@effected/config-file`.
|
|
377
|
+
*
|
|
378
|
+
* @public
|
|
379
|
+
*/
|
|
380
|
+
declare const XdgConfig: {
|
|
381
|
+
readonly resolver: (options: {
|
|
382
|
+
readonly filename: string;
|
|
383
|
+
}) => ConfigResolver<AppDirs | FileSystem.FileSystem | Path.Path>;
|
|
384
|
+
readonly nativeResolver: (options: {
|
|
385
|
+
readonly namespace: string;
|
|
386
|
+
readonly filename: string;
|
|
387
|
+
}) => ConfigResolver<Xdg | FileSystem.FileSystem | Path.Path>;
|
|
388
|
+
readonly savePath: (filename: string) => Effect.Effect<string, never, AppDirs | Path.Path>;
|
|
389
|
+
};
|
|
390
|
+
//#endregion
|
|
391
|
+
export { AppDirKind, type AppDirOverrides, AppDirs, AppDirsError, type AppDirsOptions, type AppDirsShape, CurrentPlatform, NativeDirs, ResolvedAppDirs, Xdg, XdgConfig, XdgEnvError, XdgPaths, XdgPlatform };
|
|
392
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NativeDirs } from "./NativeDirs.js";
|
|
2
|
+
import { CurrentPlatform, Xdg, XdgEnvError, XdgPaths, XdgPlatform } from "./Xdg.js";
|
|
3
|
+
import { AppDirKind, AppDirs, AppDirsError, ResolvedAppDirs } from "./AppDirs.js";
|
|
4
|
+
import { XdgConfig } from "./XdgConfig.js";
|
|
5
|
+
|
|
6
|
+
export { AppDirKind, AppDirs, AppDirsError, CurrentPlatform, NativeDirs, ResolvedAppDirs, Xdg, XdgConfig, XdgEnvError, XdgPaths, XdgPlatform };
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effected/xdg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "XDG Base Directory resolution for Effect: environment paths, app-namespaced directories, native OS conventions and config-file resolvers.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"xdg",
|
|
8
|
+
"basedir",
|
|
9
|
+
"appdirs",
|
|
10
|
+
"config",
|
|
11
|
+
"paths",
|
|
12
|
+
"effect",
|
|
13
|
+
"effected"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/spencerbeggs/effected/tree/main/packages/xdg#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/spencerbeggs/effected/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/spencerbeggs/effected.git",
|
|
22
|
+
"directory": "packages/xdg"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": {
|
|
26
|
+
"name": "C. Spencer Beggs",
|
|
27
|
+
"email": "spencer@beggs.codes",
|
|
28
|
+
"url": "https://spencerbeg.gs"
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"type": "module",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./index.d.ts",
|
|
35
|
+
"import": "./index.js"
|
|
36
|
+
},
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@effected/config-file": "0.1.0",
|
|
41
|
+
"@effected/walker": "0.1.0",
|
|
42
|
+
"effect": "4.0.0-beta.98"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=24.11.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.58.9"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|