@worldapptechnologies/nulu-home-paths 0.0.0-stage → 1.6.4
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/LICENSE +21 -0
- package/README.md +111 -2
- package/lib/index.js +107 -0
- package/lib/types/index.d.ts +73 -0
- package/package.json +32 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
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/README.md
CHANGED
|
@@ -1,3 +1,112 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Shared resolution of the Nulu Harness home and user-data paths for packages that need one consistent root, tilde expansion, and stable watch paths."
|
|
3
|
+
kind: "package-library"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @worldapptechnologies/nulu-home-paths
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
`@worldapptechnologies/nulu-home-paths` lets package authors resolve one Nulu Harness data root and derive child paths from it. An explicit path wins over `$NULU_HOME`, which wins over `~/.nulu`; blank environment values are ignored. Its public helpers can render the root without revealing an absolute machine path, expand only bare or current-user tilde forms, and canonicalize watch targets whose final components do not yet exist. Use it as a direct library dependency, not through `cordis.yml`.
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Use this package](#use-this-package)
|
|
16
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
17
|
+
- [Further Exploration](#further-exploration)
|
|
18
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
19
|
+
- [Dev Note](#dev-note)
|
|
20
|
+
|
|
21
|
+
-----
|
|
22
|
+
|
|
23
|
+
<a id="use-this-package"></a>
|
|
24
|
+
## Use this package
|
|
25
|
+
|
|
26
|
+
Use these helpers wherever a package must agree with the rest of the harness about where user data lives: resolve the home once, then derive every child path from it.
|
|
27
|
+
|
|
28
|
+
### Resolving the home
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { resolveNuluHome, nuluHomePath, nuluCachePath } from '@worldapptechnologies/nulu-home-paths'
|
|
32
|
+
|
|
33
|
+
const home = resolveNuluHome() // configured path, else $NULU_HOME, else ~/.nulu
|
|
34
|
+
const settings = nuluHomePath('settings') // join one child onto the resolved home
|
|
35
|
+
const cache = nuluCachePath('models') // $NULU_HOME/cache/models, default ~/.nulu/cache/models
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
An explicit configured path has the highest precedence, then `$NULU_HOME`, then the default `~/.nulu`. An empty or whitespace-only `$NULU_HOME` is treated as unset, so a blank override never resolves the home to the current working directory.
|
|
39
|
+
|
|
40
|
+
`nuluCachePath(...segments)` derives paths from the resolved home's `cache` directory. With no segments it returns the cache directory itself. Pass an initial options object, `nuluCachePath({ nuluHome: home }, ...segments)`, to use an explicit configured home with the same precedence and tilde expansion. It returns an absolute path without creating directories.
|
|
41
|
+
|
|
42
|
+
### Displaying a home
|
|
43
|
+
|
|
44
|
+
For user-facing paths, render the root symbolically rather than as a machine path: the default home displays as `~/.nulu` and any configured home displays as `$NULU_HOME`. The display form never leaks an absolute machine path.
|
|
45
|
+
|
|
46
|
+
### Expanding user paths
|
|
47
|
+
|
|
48
|
+
`expandHomePath` expands a leading `~`, `~/`, or `~\` against the operating-system home and leaves everything else untouched — non-tilde paths and named-user forms such as `~alice/...` pass through unchanged.
|
|
49
|
+
|
|
50
|
+
### Canonicalizing watch paths
|
|
51
|
+
|
|
52
|
+
`canonicalizeWatchPath` gives a native filesystem watcher one canonical spelling of its target: the deepest existing ancestor is resolved through `realpath` and any missing suffix is restored, so a file or directory can be watched before it is created. This prevents Windows from treating a regular-file ancestor as ordinary absence, and prevents 8.3 short-name aliases from mixing with the long paths the native watcher backend emits.
|
|
53
|
+
|
|
54
|
+
-----
|
|
55
|
+
|
|
56
|
+
<a id="understand-the-implementation"></a>
|
|
57
|
+
## Understand the implementation
|
|
58
|
+
|
|
59
|
+
<details>
|
|
60
|
+
<summary>Implementation internals — click to expand</summary>
|
|
61
|
+
|
|
62
|
+
The package is built on one principle: all harness user data lives under one root, and every other helper derives from that decision.
|
|
63
|
+
|
|
64
|
+
### Source map
|
|
65
|
+
|
|
66
|
+
| File | Role |
|
|
67
|
+
|---|---|
|
|
68
|
+
| [`src/index.ts`](src/index.ts) | Home resolution, path joining, display, tilde expansion, and watch-path canonicalization |
|
|
69
|
+
| — | No runtime invariant companion is published; this pure utility owns no event stream or mutable runtime data; its resolution rules and value algebra are enforced by unit tests. |
|
|
70
|
+
|
|
71
|
+
### Resolution rules
|
|
72
|
+
|
|
73
|
+
`resolveNuluHome` reads the explicit override, then `$NULU_HOME`, then falls back to the operating-system home joined with `.nulu`. The chosen value is tilde-expanded and normalized to an absolute path; `nuluHomePath` joins child segments with Node's platform path rules. `nuluHomeDisplay` compares the resolved path against the default root and returns the symbolic label, so a configured home never leaks its absolute path.
|
|
74
|
+
|
|
75
|
+
### Canonicalization mechanics
|
|
76
|
+
|
|
77
|
+
`canonicalizeWatchPath` walks up from the target until it finds an existing ancestor, resolves it with `realpath`, proves it is an enumerable directory, and restores the missing suffix. Errors other than absence propagate, and a missing-suffix ancestor that is not a directory is rejected.
|
|
78
|
+
|
|
79
|
+
</details>
|
|
80
|
+
|
|
81
|
+
-----
|
|
82
|
+
|
|
83
|
+
<a id="further-exploration"></a>
|
|
84
|
+
## Further Exploration
|
|
85
|
+
|
|
86
|
+
Read these pages when you need the launcher or the consumers that depend on a single home root.
|
|
87
|
+
|
|
88
|
+
- [Boot package](../../boot/app-boot/README.md) — the launcher that resolves the home before any plugin mounts.
|
|
89
|
+
- [Shell environment](../../shell/shell-env/README.md) — how `NULU_HOME` reaches model shell calls.
|
|
90
|
+
- [Anonymous user id](../../identity/anonymous-user-id/README.md) — a stored identity file under the resolved home.
|
|
91
|
+
|
|
92
|
+
-----
|
|
93
|
+
|
|
94
|
+
## Known Limitations and Deferred Work
|
|
95
|
+
|
|
96
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
These limits define when the helpers are not the right tool. They are current package constraints, not a task backlog.
|
|
100
|
+
|
|
101
|
+
- **Expansion is deliberately narrow** — only bare `~`, `~/...`, and `~\...` use the current operating-system home; named-user forms such as `~alice/...`, environment variables, and shell expressions remain unchanged.
|
|
102
|
+
- **Canonicalization reads but never mutates** — `canonicalizeWatchPath` performs `realpath` probes and propagates errors other than absence; callers still own directory creation, permissions, and trust policy for the resulting path.
|
|
103
|
+
|
|
104
|
+
<a id="dev-note"></a>
|
|
105
|
+
### Dev Note
|
|
106
|
+
|
|
107
|
+
<details>
|
|
108
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
109
|
+
|
|
110
|
+
None.
|
|
111
|
+
|
|
112
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { opendir, realpath } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
//#region lib/types/index.js
|
|
5
|
+
/**
|
|
6
|
+
* Shared filesystem path helpers for Nulu Harness user data.
|
|
7
|
+
*
|
|
8
|
+
* @module @worldapptechnologies/nulu-home-paths
|
|
9
|
+
*/
|
|
10
|
+
/** Directory name for the default Nulu Harness home under the OS home. */
|
|
11
|
+
const NULU_HOME_DIR_NAME = ".nulu";
|
|
12
|
+
/** Stable user-facing display form for the default Nulu Harness home. */
|
|
13
|
+
const DEFAULT_NULU_HOME_DISPLAY = `~/${NULU_HOME_DIR_NAME}`;
|
|
14
|
+
/** Environment variable that overrides the default Nulu Harness home. */
|
|
15
|
+
const NULU_HOME_ENV = "NULU_HOME";
|
|
16
|
+
/**
|
|
17
|
+
* Give a native filesystem watcher one canonical spelling of a path, even
|
|
18
|
+
* when its final components do not exist yet. The deepest existing ancestor
|
|
19
|
+
* is resolved through {@link realpath}; when a suffix is missing, that
|
|
20
|
+
* ancestor is also proved to be an enumerable directory before the suffix is
|
|
21
|
+
* restored. This prevents Windows from treating a regular-file ancestor as
|
|
22
|
+
* ordinary absence, and prevents short-name aliases from being mixed with
|
|
23
|
+
* long paths emitted by the native watcher backend.
|
|
24
|
+
* @param path - Watch target or root, resolved against the current directory.
|
|
25
|
+
* @returns the target with its existing ancestor canonicalized.
|
|
26
|
+
* @throws when ancestor traversal encounters an error other than absence, or
|
|
27
|
+
* the existing ancestor of a missing suffix is not an enumerable directory.
|
|
28
|
+
*/
|
|
29
|
+
async function canonicalizeWatchPath(path) {
|
|
30
|
+
let current = resolve(path);
|
|
31
|
+
const missing = [];
|
|
32
|
+
while (true) try {
|
|
33
|
+
const canonical = await realpath(current);
|
|
34
|
+
if (missing.length > 0) await (await opendir(canonical)).close();
|
|
35
|
+
return join(canonical, ...missing.reverse());
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code !== "ENOENT") throw error;
|
|
38
|
+
const parent = dirname(current);
|
|
39
|
+
/* v8 ignore next -- a filesystem root exists, so traversal resolves before this guard */
|
|
40
|
+
if (parent === current) throw error;
|
|
41
|
+
missing.push(basename(current));
|
|
42
|
+
current = parent;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the default Nulu Harness home using Node's platform path rules.
|
|
47
|
+
* @returns the absolute default harness home path.
|
|
48
|
+
*/
|
|
49
|
+
function defaultNuluHome() {
|
|
50
|
+
return join(homedir(), NULU_HOME_DIR_NAME);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Expand supported tilde prefixes against the operating-system home.
|
|
54
|
+
* @param path - configured path that may begin with `~`, `~/`, or `~\`.
|
|
55
|
+
* @returns the expanded path, or the original value when no supported prefix is present.
|
|
56
|
+
*/
|
|
57
|
+
function expandHomePath(path) {
|
|
58
|
+
if (path === "~") return homedir();
|
|
59
|
+
if (path.startsWith("~/") || path.startsWith("~\\")) return join(homedir(), path.slice(2));
|
|
60
|
+
return path;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the single-root Nulu Harness home.
|
|
64
|
+
*
|
|
65
|
+
* Precedence, highest first: an explicit configured path, `$NULU_HOME`, then
|
|
66
|
+
* `~/.nulu`. The harness keeps all user data under one root. An empty or
|
|
67
|
+
* whitespace-only `$NULU_HOME` is treated as unset, so a blank override never
|
|
68
|
+
* resolves the home to the current working directory.
|
|
69
|
+
* @param configured - explicit harness-home override, which has highest precedence.
|
|
70
|
+
* @param env - environment mapping used to read `NULU_HOME`.
|
|
71
|
+
* @returns the normalized absolute harness home path.
|
|
72
|
+
*/
|
|
73
|
+
function resolveNuluHome(configured, env = process.env) {
|
|
74
|
+
const fromEnv = env[NULU_HOME_ENV];
|
|
75
|
+
return resolve(expandHomePath(configured ?? (fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : defaultNuluHome())));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Join path segments onto the resolved Nulu Harness home.
|
|
79
|
+
* @param segments - path segments appended to the Harness home; an empty list returns the home itself.
|
|
80
|
+
* @returns the normalized absolute joined path.
|
|
81
|
+
*/
|
|
82
|
+
function nuluHomePath(...segments) {
|
|
83
|
+
return join(resolveNuluHome(), ...segments);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Join path segments onto the resolved Harness home's `cache` directory without creating it; no arguments returns the directory itself.
|
|
87
|
+
* @param optionsOrSegment - explicit home override, or the first path segment; omission uses the default home resolution.
|
|
88
|
+
* @param segments - additional path segments after the first child, if any.
|
|
89
|
+
* @returns the normalized absolute cache path.
|
|
90
|
+
*/
|
|
91
|
+
function nuluCachePath(optionsOrSegment = {}, ...segments) {
|
|
92
|
+
if (typeof optionsOrSegment === "string") return nuluHomePath("cache", optionsOrSegment, ...segments);
|
|
93
|
+
return join(resolveNuluHome(optionsOrSegment.nuluHome), "cache", ...segments);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Describe a resolved harness home symbolically for user-facing display.
|
|
97
|
+
*
|
|
98
|
+
* It never returns an absolute machine path: the default home is labelled
|
|
99
|
+
* `~/.nulu`, and any configured home is labelled `$NULU_HOME`.
|
|
100
|
+
* @param resolvedHome - the absolute path returned by {@link resolveNuluHome}.
|
|
101
|
+
* @returns `~/.nulu` for the default home, otherwise `$NULU_HOME`.
|
|
102
|
+
*/
|
|
103
|
+
function nuluHomeDisplay(resolvedHome) {
|
|
104
|
+
return resolvedHome === resolve(defaultNuluHome()) ? DEFAULT_NULU_HOME_DISPLAY : `$${NULU_HOME_ENV}`;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { DEFAULT_NULU_HOME_DISPLAY, NULU_HOME_DIR_NAME, NULU_HOME_ENV, canonicalizeWatchPath, defaultNuluHome, expandHomePath, nuluCachePath, nuluHomeDisplay, nuluHomePath, resolveNuluHome };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filesystem path helpers for Nulu Harness user data.
|
|
3
|
+
*
|
|
4
|
+
* @module @worldapptechnologies/nulu-home-paths
|
|
5
|
+
*/
|
|
6
|
+
/** Directory name for the default Nulu Harness home under the OS home. */
|
|
7
|
+
export declare const NULU_HOME_DIR_NAME = ".nulu";
|
|
8
|
+
/** Stable user-facing display form for the default Nulu Harness home. */
|
|
9
|
+
export declare const DEFAULT_NULU_HOME_DISPLAY = "~/.nulu";
|
|
10
|
+
/** Environment variable that overrides the default Nulu Harness home. */
|
|
11
|
+
export declare const NULU_HOME_ENV = "NULU_HOME";
|
|
12
|
+
/**
|
|
13
|
+
* Give a native filesystem watcher one canonical spelling of a path, even
|
|
14
|
+
* when its final components do not exist yet. The deepest existing ancestor
|
|
15
|
+
* is resolved through {@link realpath}; when a suffix is missing, that
|
|
16
|
+
* ancestor is also proved to be an enumerable directory before the suffix is
|
|
17
|
+
* restored. This prevents Windows from treating a regular-file ancestor as
|
|
18
|
+
* ordinary absence, and prevents short-name aliases from being mixed with
|
|
19
|
+
* long paths emitted by the native watcher backend.
|
|
20
|
+
* @param path - Watch target or root, resolved against the current directory.
|
|
21
|
+
* @returns the target with its existing ancestor canonicalized.
|
|
22
|
+
* @throws when ancestor traversal encounters an error other than absence, or
|
|
23
|
+
* the existing ancestor of a missing suffix is not an enumerable directory.
|
|
24
|
+
*/
|
|
25
|
+
export declare function canonicalizeWatchPath(path: string): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the default Nulu Harness home using Node's platform path rules.
|
|
28
|
+
* @returns the absolute default harness home path.
|
|
29
|
+
*/
|
|
30
|
+
export declare function defaultNuluHome(): string;
|
|
31
|
+
/**
|
|
32
|
+
* Expand supported tilde prefixes against the operating-system home.
|
|
33
|
+
* @param path - configured path that may begin with `~`, `~/`, or `~\`.
|
|
34
|
+
* @returns the expanded path, or the original value when no supported prefix is present.
|
|
35
|
+
*/
|
|
36
|
+
export declare function expandHomePath(path: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the single-root Nulu Harness home.
|
|
39
|
+
*
|
|
40
|
+
* Precedence, highest first: an explicit configured path, `$NULU_HOME`, then
|
|
41
|
+
* `~/.nulu`. The harness keeps all user data under one root. An empty or
|
|
42
|
+
* whitespace-only `$NULU_HOME` is treated as unset, so a blank override never
|
|
43
|
+
* resolves the home to the current working directory.
|
|
44
|
+
* @param configured - explicit harness-home override, which has highest precedence.
|
|
45
|
+
* @param env - environment mapping used to read `NULU_HOME`.
|
|
46
|
+
* @returns the normalized absolute harness home path.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveNuluHome(configured?: string, env?: Record<string, string | undefined>): string;
|
|
49
|
+
/**
|
|
50
|
+
* Join path segments onto the resolved Nulu Harness home.
|
|
51
|
+
* @param segments - path segments appended to the Harness home; an empty list returns the home itself.
|
|
52
|
+
* @returns the normalized absolute joined path.
|
|
53
|
+
*/
|
|
54
|
+
export declare function nuluHomePath(...segments: string[]): string;
|
|
55
|
+
/**
|
|
56
|
+
* Join path segments onto the resolved Harness home's `cache` directory without creating it; no arguments returns the directory itself.
|
|
57
|
+
* @param optionsOrSegment - explicit home override, or the first path segment; omission uses the default home resolution.
|
|
58
|
+
* @param segments - additional path segments after the first child, if any.
|
|
59
|
+
* @returns the normalized absolute cache path.
|
|
60
|
+
*/
|
|
61
|
+
export declare function nuluCachePath(optionsOrSegment?: {
|
|
62
|
+
nuluHome?: string;
|
|
63
|
+
} | string, ...segments: string[]): string;
|
|
64
|
+
/**
|
|
65
|
+
* Describe a resolved harness home symbolically for user-facing display.
|
|
66
|
+
*
|
|
67
|
+
* It never returns an absolute machine path: the default home is labelled
|
|
68
|
+
* `~/.nulu`, and any configured home is labelled `$NULU_HOME`.
|
|
69
|
+
* @param resolvedHome - the absolute path returned by {@link resolveNuluHome}.
|
|
70
|
+
* @returns `~/.nulu` for the default home, otherwise `$NULU_HOME`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function nuluHomeDisplay(resolvedHome: string): string;
|
|
73
|
+
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@worldapptechnologies/nulu-home-paths",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Shared filesystem path helpers for the Nulu Harness",
|
|
4
|
+
"version": "1.6.4",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/worldapptechnologies/nulu-harness.git",
|
|
11
|
+
"directory": "packages/util/home-paths"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./src/*": "./src/*",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"lib/index.js",
|
|
26
|
+
"lib/types/**/*.d.ts"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@worldapptechnologies/cordis": "^4.0.2"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@worldapptechnologies/cordis": "^4.0.2"
|
|
34
|
+
}
|
|
6
35
|
}
|