dsh-plugin-wsl-env 0.7.5 → 0.8.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 +278 -0
- package/CONTRIBUTING.md +129 -39
- package/README.md +20 -20
- package/docs/ARCHITECTURE.md +6 -3
- package/docs/LIMITATIONS.md +13 -0
- package/docs/README.zh.md +20 -20
- package/lib/agent-confined.d.ts +42 -0
- package/lib/agent-confined.js +25 -41
- package/lib/agent-errors.d.ts +31 -0
- package/lib/agent-errors.js +19 -16
- package/lib/agent-exec.d.ts +159 -0
- package/lib/agent-exec.js +151 -165
- package/lib/agent-protocol.d.ts +192 -0
- package/lib/agent-protocol.js +76 -100
- package/lib/agent-shared.d.ts +27 -0
- package/lib/agent-shared.js +12 -12
- package/lib/agent.d.ts +353 -0
- package/lib/agent.js +536 -552
- package/lib/auto-preset.d.ts +110 -0
- package/lib/auto-preset.js +157 -141
- package/lib/bwrap.d.ts +90 -0
- package/lib/bwrap.js +18 -18
- package/lib/file-reference-wsl.d.ts +160 -0
- package/lib/file-reference-wsl.js +192 -173
- package/lib/fs-decisions.d.ts +107 -0
- package/lib/fs-decisions.js +64 -60
- package/lib/fs-routing.d.ts +182 -0
- package/lib/fs-routing.js +173 -116
- package/lib/fs-substrate.d.ts +258 -0
- package/lib/fs-substrate.js +248 -248
- package/lib/fs.d.ts +10 -0
- package/lib/fs.js +5 -0
- package/lib/fsio-agent.d.ts +307 -0
- package/lib/fsio-agent.js +414 -410
- package/lib/fsio-text.d.ts +115 -0
- package/lib/fsio-text.js +104 -84
- package/lib/git-route.d.ts +100 -0
- package/lib/git-route.js +46 -47
- package/lib/index.d.ts +1003 -0
- package/lib/index.js +1101 -1003
- package/lib/listing.d.ts +114 -0
- package/lib/listing.js +68 -55
- package/lib/paths.d.ts +182 -0
- package/lib/paths.js +134 -109
- package/lib/picker.d.ts +266 -0
- package/lib/picker.js +371 -351
- package/lib/ports.d.ts +44 -0
- package/lib/ports.js +39 -29
- package/lib/preset-choice.d.ts +33 -0
- package/lib/preset-choice.js +15 -7
- package/lib/probe-cache.d.ts +40 -0
- package/lib/probe-cache.js +35 -29
- package/lib/sandbox-core.d.ts +79 -0
- package/lib/sandbox-core.js +71 -88
- package/lib/sandbox.d.ts +48 -0
- package/lib/sandbox.js +4 -4
- package/lib/search-exec.d.ts +96 -0
- package/lib/search-exec.js +52 -79
- package/lib/search-route.d.ts +75 -0
- package/lib/search-route.js +30 -31
- package/lib/shell-env.d.ts +88 -0
- package/lib/shell-env.js +78 -74
- package/lib/shell.d.ts +10 -0
- package/lib/shell.js +5 -0
- package/lib/subprocess.d.ts +313 -0
- package/lib/subprocess.js +267 -262
- package/lib/terminal-activity.d.ts +98 -0
- package/lib/terminal-activity.js +29 -39
- package/lib/terminal-route.d.ts +27 -0
- package/lib/terminal-route.js +16 -8
- package/lib/watcher.d.ts +77 -0
- package/lib/watcher.js +120 -126
- package/lib/workspace-files-route.d.ts +132 -0
- package/lib/workspace-files-route.js +73 -81
- package/lib/workspace-files-wsl.d.ts +257 -0
- package/lib/workspace-files-wsl.js +311 -251
- package/lib/wsl.d.ts +266 -0
- package/lib/wsl.js +144 -155
- package/package.json +68 -20
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The distro-side traversal strategy for `@` completion — the plugin's
|
|
3
|
+
* ready answer to RFC addendum ① (docs/upstream/rfc-wsl-workspaces.md).
|
|
4
|
+
*
|
|
5
|
+
* ## The seam this consumes
|
|
6
|
+
*
|
|
7
|
+
* `@deepseek-ai/dsh-file-reference-local` funnels every workspace traversal
|
|
8
|
+
* through three module-internal functions: `readWorkspaceRoot` and
|
|
9
|
+
* `readDirectory` (both `node:fs/promises` readdir with `withFileTypes`) and
|
|
10
|
+
* `resolveDisplayDirectory` (a lexical resolve plus a per-segment `lstat`
|
|
11
|
+
* walk that refuses symlink and non-directory components). Everything else —
|
|
12
|
+
* the fuzzy ranking, the bounded index, the generation/invalidation dance,
|
|
13
|
+
* the excluded-directory list — is pure logic over the entries those three
|
|
14
|
+
* return. Addendum ① proposes exposing the three as an injectable strategy
|
|
15
|
+
* with the current implementations as defaults; this module is the consumer
|
|
16
|
+
* side, built and tested against that interface so the takeover lands the
|
|
17
|
+
* day the seam does.
|
|
18
|
+
*
|
|
19
|
+
* It is deliberately NOT wired into cordis.patch.yml yet: with the seam
|
|
20
|
+
* absent, reaching the funnels would mean subclassing past module-internal
|
|
21
|
+
* functions or re-implementing the ranking engine — the behavior-regression
|
|
22
|
+
* path the RFC rules out. The module ships, tested, until upstream speaks.
|
|
23
|
+
*
|
|
24
|
+
* ## Semantics, held faithful where the seam allows
|
|
25
|
+
*
|
|
26
|
+
* - Entries keep upstream's Dirent shape: `{name, isDirectory(), isFile()}`.
|
|
27
|
+
* The distro listing uses `find -printf '%y\t%f'` rather than `ls -1ALp`
|
|
28
|
+
* precisely for this: `%y` reports `l` for symlinks, which the parse skips
|
|
29
|
+
* the way a readdir Dirent is neither file nor directory. `ls -1ALp` would
|
|
30
|
+
* resolve links through and change which candidates exist.
|
|
31
|
+
* - `resolveDisplayDirectory` refuses a symlink or non-directory component
|
|
32
|
+
* with one agent exec that walks the (lexically normalized) segments —
|
|
33
|
+
* the same verdict upstream reaches with N sequential lstats, and unlike
|
|
34
|
+
* `realpath`, refusing is the point, not resolving.
|
|
35
|
+
* - A directory the traversal cannot read yields `[]` (upstream's readdir
|
|
36
|
+
* catch); an aborted caller propagates the abort instead.
|
|
37
|
+
* - The returned absolutes stay in the root's own coordinate system (UNC for
|
|
38
|
+
* a distro workspace), because upstream joins children with the host
|
|
39
|
+
* `node:path` — a POSIX path from this layer would be mangled by it.
|
|
40
|
+
*
|
|
41
|
+
* This is a TypeScript source built to `lib/file-reference-wsl.js`; edit THIS
|
|
42
|
+
* file and run `pnpm run build` — the artifact under `lib/` is generated, and
|
|
43
|
+
* `pnpm test` fails when it drifts.
|
|
44
|
+
*
|
|
45
|
+
* @module dsh-plugin-wsl/file-reference-wsl
|
|
46
|
+
*/
|
|
47
|
+
import type { WslAgent } from "./agent.js";
|
|
48
|
+
/**
|
|
49
|
+
* Upstream's Dirent shape the ranking engine reads: what a `withFileTypes`
|
|
50
|
+
* readdir yields and what the distro listing parses into.
|
|
51
|
+
*/
|
|
52
|
+
export interface TraversalDirent {
|
|
53
|
+
/** The child's name. */
|
|
54
|
+
name: string;
|
|
55
|
+
/** The directory verdict. */
|
|
56
|
+
isDirectory: () => boolean;
|
|
57
|
+
/** The regular-file verdict. */
|
|
58
|
+
isFile: () => boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The traversal seam `WorkspaceFileSearch` consumes — the three module-internal
|
|
62
|
+
* funnels of `@deepseek-ai/dsh-file-reference-local`, per RFC addendum ①.
|
|
63
|
+
*/
|
|
64
|
+
export interface FileReferenceTraversal {
|
|
65
|
+
/** The root's entries. */
|
|
66
|
+
readWorkspaceRoot: (absolute: string, signal: AbortSignal) => Promise<TraversalDirent[]>;
|
|
67
|
+
/** One directory's entries; an unreadable one yields []. */
|
|
68
|
+
readDirectory: (absolute: string, signal: AbortSignal) => Promise<TraversalDirent[]>;
|
|
69
|
+
/** The resolved display directory, or undefined when refused. */
|
|
70
|
+
resolveDisplayDirectory: (root: string, displayDirectory: string, signal: AbortSignal) => Promise<string | undefined>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Lexically normalize a `/`-separated display directory against its root.
|
|
74
|
+
* Upstream resolves `resolve(root, displayDirectory)` and refuses a result
|
|
75
|
+
* outside the root, so `a/../b` lands on `b` while `..` past the root is
|
|
76
|
+
* refused outright — the walk below never sees a `..` segment.
|
|
77
|
+
*
|
|
78
|
+
* @param displayDirectory - the query text before the fragment, possibly "".
|
|
79
|
+
* @returns the normalized segments, or undefined when the display directory
|
|
80
|
+
* escapes the root.
|
|
81
|
+
*/
|
|
82
|
+
export declare function normalizeDisplaySegments(displayDirectory: string): string[] | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* The distro argv for one directory listing: `find -printf '%y\t%f'` over the
|
|
85
|
+
* contained directory. The containment guard is the same shape the picker and
|
|
86
|
+
* workspace-files use — `realpath` the requested directory, refuse anything
|
|
87
|
+
* the root does not contain — and the `%y` type char is what keeps symlink
|
|
88
|
+
* semantics identical to a readdir Dirent (skipped, not followed).
|
|
89
|
+
*
|
|
90
|
+
* @param linuxRoot - the workspace root, POSIX spelling.
|
|
91
|
+
* @param linuxDir - the directory to list, POSIX spelling.
|
|
92
|
+
* @param maxEntries - the listing cap (head -n bound).
|
|
93
|
+
* @returns the agent exec argv.
|
|
94
|
+
*/
|
|
95
|
+
export declare function traversalArgv(linuxRoot: string, linuxDir: string, maxEntries: number): string[];
|
|
96
|
+
/**
|
|
97
|
+
* Parse one `%y\t%f` listing into upstream's Dirent-like entries.
|
|
98
|
+
* `d` is a directory and `f` a file; every other type char — `l` for
|
|
99
|
+
* symlinks, devices, sockets — is skipped, matching a readdir Dirent.
|
|
100
|
+
*
|
|
101
|
+
* @param stdout - the raw listing.
|
|
102
|
+
* @returns the parsed entries.
|
|
103
|
+
*/
|
|
104
|
+
export declare function parseTraversalListing(stdout: string | Buffer): Array<{
|
|
105
|
+
name: string;
|
|
106
|
+
isDirectory: () => boolean;
|
|
107
|
+
isFile: () => boolean;
|
|
108
|
+
}>;
|
|
109
|
+
/**
|
|
110
|
+
* The argv for the per-segment walk behind `resolveDisplayDirectory`: each
|
|
111
|
+
* segment must exist, be a directory, and not be a symlink — upstream's
|
|
112
|
+
* lstat verdicts, checked in one exec. Segments ride as argv elements, never
|
|
113
|
+
* interpolated into the script.
|
|
114
|
+
*
|
|
115
|
+
* @param linuxRoot - the workspace root, POSIX spelling.
|
|
116
|
+
* @param segments - the normalized display segments below the root.
|
|
117
|
+
* @returns the agent exec argv.
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolveDirectoryArgv(linuxRoot: string, segments: string[]): string[];
|
|
120
|
+
/**
|
|
121
|
+
* The host-default traversal — the node:fs implementations the seam keeps
|
|
122
|
+
* as upstream's defaults, replicated here so the prototype answers host
|
|
123
|
+
* workspaces without the seam and so tests can inject substitutes for it.
|
|
124
|
+
* (Upstream owns the real defaults; this copy exists only until the takeover.)
|
|
125
|
+
*
|
|
126
|
+
* @param deps - the node:fs/promises functions, injectable for tests.
|
|
127
|
+
* @returns the strategy object with the upstream default semantics.
|
|
128
|
+
*/
|
|
129
|
+
export declare function hostFileReferenceTraversal(deps: {
|
|
130
|
+
/** The readdir implementation. */
|
|
131
|
+
readdir: typeof import("node:fs/promises").readdir;
|
|
132
|
+
/** The lstat implementation. */
|
|
133
|
+
lstat: typeof import("node:fs/promises").lstat;
|
|
134
|
+
}): FileReferenceTraversal;
|
|
135
|
+
/**
|
|
136
|
+
* The distro-routed traversal strategy. Each method routes by coordinate:
|
|
137
|
+
* a `\\wsl.localhost\<distro>` UNC rides the resident agent (the search
|
|
138
|
+
* root and every absolute upstream passes stay in the UNC form it joined
|
|
139
|
+
* them with); anything else defers to the default traversal. The search
|
|
140
|
+
* root doubles as the containment root for the distro listings' guard, so
|
|
141
|
+
* the seam wires one strategy per `WorkspaceFileSearch` — exactly where
|
|
142
|
+
* `WorkspaceFileSearch(root, config, traversal)` would take it.
|
|
143
|
+
*
|
|
144
|
+
* @param options - the strategy's collaborators.
|
|
145
|
+
* @returns the strategy object the seam's `WorkspaceFileSearch` would take.
|
|
146
|
+
*/
|
|
147
|
+
export declare function wslFileReferenceTraversal({ root, runnerFor, defaultTraversal, maxEntries }: {
|
|
148
|
+
/** The search root, the UNC form upstream passes. */
|
|
149
|
+
root: string;
|
|
150
|
+
/**
|
|
151
|
+
* The agent runner for one distro; tests substitute a fake (a real wiring
|
|
152
|
+
* passes sharedAgent).
|
|
153
|
+
*/
|
|
154
|
+
runnerFor: (distro: string) => Pick<WslAgent, "exec">;
|
|
155
|
+
/** The host-default strategy. */
|
|
156
|
+
defaultTraversal: FileReferenceTraversal;
|
|
157
|
+
/** The listing cap, upstream's index budget. */
|
|
158
|
+
maxEntries?: number;
|
|
159
|
+
}): FileReferenceTraversal;
|
|
160
|
+
export default wslFileReferenceTraversal;
|
|
@@ -38,39 +38,41 @@
|
|
|
38
38
|
* a distro workspace), because upstream joins children with the host
|
|
39
39
|
* `node:path` — a POSIX path from this layer would be mangled by it.
|
|
40
40
|
*
|
|
41
|
+
* This is a TypeScript source built to `lib/file-reference-wsl.js`; edit THIS
|
|
42
|
+
* file and run `pnpm run build` — the artifact under `lib/` is generated, and
|
|
43
|
+
* `pnpm test` fails when it drifts.
|
|
44
|
+
*
|
|
41
45
|
* @module dsh-plugin-wsl/file-reference-wsl
|
|
42
46
|
*/
|
|
43
|
-
|
|
44
47
|
import { isWslUnc, posixToUnc, uncToPosix } from "./paths.js";
|
|
45
|
-
|
|
46
48
|
/** Upstream's default cap on one workspace index; the per-directory listing
|
|
47
49
|
* rides the same bound, since one level can't usefully exceed the budget. */
|
|
48
50
|
const DEFAULT_MAX_ENTRIES = 50000;
|
|
49
|
-
|
|
50
51
|
/**
|
|
51
52
|
* Lexically normalize a `/`-separated display directory against its root.
|
|
52
53
|
* Upstream resolves `resolve(root, displayDirectory)` and refuses a result
|
|
53
54
|
* outside the root, so `a/../b` lands on `b` while `..` past the root is
|
|
54
55
|
* refused outright — the walk below never sees a `..` segment.
|
|
55
56
|
*
|
|
56
|
-
* @param
|
|
57
|
-
* @returns
|
|
58
|
-
*
|
|
57
|
+
* @param displayDirectory - the query text before the fragment, possibly "".
|
|
58
|
+
* @returns the normalized segments, or undefined when the display directory
|
|
59
|
+
* escapes the root.
|
|
59
60
|
*/
|
|
60
61
|
export function normalizeDisplaySegments(displayDirectory) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const segments = [];
|
|
63
|
+
for (const segment of String(displayDirectory).split("/")) {
|
|
64
|
+
if (segment === "" || segment === ".")
|
|
65
|
+
continue;
|
|
66
|
+
if (segment === "..") {
|
|
67
|
+
if (segments.length === 0)
|
|
68
|
+
return undefined;
|
|
69
|
+
segments.pop();
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
segments.push(segment);
|
|
68
73
|
}
|
|
69
|
-
segments
|
|
70
|
-
}
|
|
71
|
-
return segments;
|
|
74
|
+
return segments;
|
|
72
75
|
}
|
|
73
|
-
|
|
74
76
|
/**
|
|
75
77
|
* The distro argv for one directory listing: `find -printf '%y\t%f'` over the
|
|
76
78
|
* contained directory. The containment guard is the same shape the picker and
|
|
@@ -78,127 +80,131 @@ export function normalizeDisplaySegments(displayDirectory) {
|
|
|
78
80
|
* the root does not contain — and the `%y` type char is what keeps symlink
|
|
79
81
|
* semantics identical to a readdir Dirent (skipped, not followed).
|
|
80
82
|
*
|
|
81
|
-
* @param
|
|
82
|
-
* @param
|
|
83
|
-
* @param
|
|
84
|
-
* @returns
|
|
83
|
+
* @param linuxRoot - the workspace root, POSIX spelling.
|
|
84
|
+
* @param linuxDir - the directory to list, POSIX spelling.
|
|
85
|
+
* @param maxEntries - the listing cap (head -n bound).
|
|
86
|
+
* @returns the agent exec argv.
|
|
85
87
|
*/
|
|
86
88
|
export function traversalArgv(linuxRoot, linuxDir, maxEntries) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
const script = [
|
|
90
|
+
"root=$1 dir=$2 max=$3",
|
|
91
|
+
'real=$(realpath -L -- "$dir") || exit 2',
|
|
92
|
+
'case "$real" in "$root") ;; "$root"/*) ;; *) echo "OUTSIDE:$real" >&2; exit 3;; esac',
|
|
93
|
+
'[ -d "$real" ] || { echo "NOTDIR:$real" >&2; exit 4; }',
|
|
94
|
+
'find "$real" -mindepth 1 -maxdepth 1 -printf \'%y\\t%f\\n\' | head -n "$max"',
|
|
95
|
+
].join("\n");
|
|
96
|
+
return ["sh", "-c", script, "sh", linuxRoot, linuxDir, String(maxEntries)];
|
|
95
97
|
}
|
|
96
|
-
|
|
97
98
|
/**
|
|
98
99
|
* Parse one `%y\t%f` listing into upstream's Dirent-like entries.
|
|
99
100
|
* `d` is a directory and `f` a file; every other type char — `l` for
|
|
100
101
|
* symlinks, devices, sockets — is skipped, matching a readdir Dirent.
|
|
101
102
|
*
|
|
102
|
-
* @param
|
|
103
|
-
* @returns
|
|
103
|
+
* @param stdout - the raw listing.
|
|
104
|
+
* @returns the parsed entries.
|
|
104
105
|
*/
|
|
105
106
|
export function parseTraversalListing(stdout) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
const entries = [];
|
|
108
|
+
for (const line of String(stdout).split("\n")) {
|
|
109
|
+
const tab = line.indexOf("\t");
|
|
110
|
+
if (tab <= 0)
|
|
111
|
+
continue;
|
|
112
|
+
const kind = line.slice(0, tab);
|
|
113
|
+
const name = line.slice(tab + 1);
|
|
114
|
+
if (name.length === 0)
|
|
115
|
+
continue;
|
|
116
|
+
if (kind !== "d" && kind !== "f")
|
|
117
|
+
continue;
|
|
118
|
+
entries.push({
|
|
119
|
+
name,
|
|
120
|
+
isDirectory: () => kind === "d",
|
|
121
|
+
isFile: () => kind === "f",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return entries;
|
|
121
125
|
}
|
|
122
|
-
|
|
123
126
|
/**
|
|
124
127
|
* The argv for the per-segment walk behind `resolveDisplayDirectory`: each
|
|
125
128
|
* segment must exist, be a directory, and not be a symlink — upstream's
|
|
126
129
|
* lstat verdicts, checked in one exec. Segments ride as argv elements, never
|
|
127
130
|
* interpolated into the script.
|
|
128
131
|
*
|
|
129
|
-
* @param
|
|
130
|
-
* @param
|
|
131
|
-
* @returns
|
|
132
|
+
* @param linuxRoot - the workspace root, POSIX spelling.
|
|
133
|
+
* @param segments - the normalized display segments below the root.
|
|
134
|
+
* @returns the agent exec argv.
|
|
132
135
|
*/
|
|
133
136
|
export function resolveDirectoryArgv(linuxRoot, segments) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
137
|
+
const script = [
|
|
138
|
+
"root=$1",
|
|
139
|
+
'cur="$root"',
|
|
140
|
+
'for seg in "${@:2}"; do',
|
|
141
|
+
' cur="$cur/$seg"',
|
|
142
|
+
' if [ -L "$cur" ]; then echo "SYMLINK:$cur" >&2; exit 5; fi',
|
|
143
|
+
' [ -d "$cur" ] || { echo "NOTDIR:$cur" >&2; exit 4; }',
|
|
144
|
+
"done",
|
|
145
|
+
'printf \'%s\\n\' "$cur"',
|
|
146
|
+
].join("\n");
|
|
147
|
+
return ["sh", "-c", script, "sh", linuxRoot, ...segments];
|
|
145
148
|
}
|
|
146
|
-
|
|
147
149
|
/**
|
|
148
150
|
* The host-default traversal — the node:fs implementations the seam keeps
|
|
149
151
|
* as upstream's defaults, replicated here so the prototype answers host
|
|
150
152
|
* workspaces without the seam and so tests can inject substitutes for it.
|
|
151
153
|
* (Upstream owns the real defaults; this copy exists only until the takeover.)
|
|
152
154
|
*
|
|
153
|
-
* @param
|
|
155
|
+
* @param deps - the node:fs/promises functions, injectable for tests.
|
|
154
156
|
* @returns the strategy object with the upstream default semantics.
|
|
155
157
|
*/
|
|
156
158
|
export function hostFileReferenceTraversal(deps) {
|
|
157
|
-
|
|
158
|
-
signal.throwIfAborted();
|
|
159
|
-
try {
|
|
160
|
-
const entries = await deps.readdir(absolute, { withFileTypes: true });
|
|
161
|
-
signal.throwIfAborted();
|
|
162
|
-
return entries;
|
|
163
|
-
} catch (error) {
|
|
164
|
-
signal.throwIfAborted();
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
return {
|
|
169
|
-
readWorkspaceRoot: read,
|
|
170
|
-
readDirectory: async (absolute, signal) => {
|
|
171
|
-
signal.throwIfAborted();
|
|
172
|
-
try {
|
|
173
|
-
const entries = await deps.readdir(absolute, { withFileTypes: true });
|
|
174
|
-
signal.throwIfAborted();
|
|
175
|
-
return entries;
|
|
176
|
-
} catch {
|
|
177
|
-
signal.throwIfAborted();
|
|
178
|
-
return [];
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
resolveDisplayDirectory: async (root, displayDirectory, signal) => {
|
|
182
|
-
const segments = normalizeDisplaySegments(displayDirectory);
|
|
183
|
-
if (segments === undefined) return undefined;
|
|
184
|
-
let current = root;
|
|
185
|
-
for (const segment of segments) {
|
|
159
|
+
const read = async (absolute, signal) => {
|
|
186
160
|
signal.throwIfAborted();
|
|
187
|
-
current = `${current}${current.endsWith("/") ? "" : "/"}${segment}`;
|
|
188
161
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
} catch (error) {
|
|
193
|
-
signal.throwIfAborted();
|
|
194
|
-
return undefined;
|
|
162
|
+
const entries = await deps.readdir(absolute, { withFileTypes: true });
|
|
163
|
+
signal.throwIfAborted();
|
|
164
|
+
return entries;
|
|
195
165
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
166
|
+
catch (error) {
|
|
167
|
+
signal.throwIfAborted();
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
readWorkspaceRoot: read,
|
|
173
|
+
readDirectory: async (absolute, signal) => {
|
|
174
|
+
signal.throwIfAborted();
|
|
175
|
+
try {
|
|
176
|
+
const entries = await deps.readdir(absolute, { withFileTypes: true });
|
|
177
|
+
signal.throwIfAborted();
|
|
178
|
+
return entries;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
signal.throwIfAborted();
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
resolveDisplayDirectory: async (root, displayDirectory, signal) => {
|
|
186
|
+
const segments = normalizeDisplaySegments(displayDirectory);
|
|
187
|
+
if (segments === undefined)
|
|
188
|
+
return undefined;
|
|
189
|
+
let current = root;
|
|
190
|
+
for (const segment of segments) {
|
|
191
|
+
signal.throwIfAborted();
|
|
192
|
+
current = `${current}${current.endsWith("/") ? "" : "/"}${segment}`;
|
|
193
|
+
try {
|
|
194
|
+
const status = await deps.lstat(current);
|
|
195
|
+
signal.throwIfAborted();
|
|
196
|
+
if (status.isSymbolicLink() || !status.isDirectory())
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
signal.throwIfAborted();
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return current;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
200
207
|
}
|
|
201
|
-
|
|
202
208
|
/**
|
|
203
209
|
* The distro-routed traversal strategy. Each method routes by coordinate:
|
|
204
210
|
* a `\\wsl.localhost\<distro>` UNC rides the resident agent (the search
|
|
@@ -208,77 +214,90 @@ export function hostFileReferenceTraversal(deps) {
|
|
|
208
214
|
* the seam wires one strategy per `WorkspaceFileSearch` — exactly where
|
|
209
215
|
* `WorkspaceFileSearch(root, config, traversal)` would take it.
|
|
210
216
|
*
|
|
211
|
-
* @param
|
|
212
|
-
* @param {string} options.root - the search root, the UNC form upstream passes.
|
|
213
|
-
* @param {(distro: string) => object} options.runnerFor - the agent runner
|
|
214
|
-
* for one distro; tests substitute a fake (a real wiring passes sharedAgent).
|
|
215
|
-
* @param {object} options.defaultTraversal - the host-default strategy.
|
|
216
|
-
* @param {number} [options.maxEntries] - the listing cap, upstream's index budget.
|
|
217
|
+
* @param options - the strategy's collaborators.
|
|
217
218
|
* @returns the strategy object the seam's `WorkspaceFileSearch` would take.
|
|
218
219
|
*/
|
|
219
220
|
export function wslFileReferenceTraversal({ root, runnerFor, defaultTraversal, maxEntries = DEFAULT_MAX_ENTRIES }) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (result.exitCode !== 0) {
|
|
237
|
-
return onRefusal(new Error(`listing "${linuxPath}" failed: ${String(result.stderr).trim() || `exit ${result.exitCode}`}`));
|
|
238
|
-
}
|
|
239
|
-
return parseTraversalListing(result.stdout);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
signal.throwIfAborted();
|
|
242
|
-
return onRefusal(error);
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
readWorkspaceRoot(absolute, signal) {
|
|
248
|
-
if (!isWslUnc(absolute)) return defaultTraversal.readWorkspaceRoot(absolute, signal);
|
|
249
|
-
return listDistro(absolute, signal, (error) => {
|
|
250
|
-
throw error;
|
|
251
|
-
});
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
readDirectory(absolute, signal) {
|
|
255
|
-
if (!isWslUnc(absolute)) return defaultTraversal.readDirectory(absolute, signal);
|
|
256
|
-
return listDistro(absolute, signal, () => []);
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
async resolveDisplayDirectory(rootOf, displayDirectory, signal) {
|
|
260
|
-
signal.throwIfAborted();
|
|
261
|
-
if (!isWslUnc(rootOf)) return defaultTraversal.resolveDisplayDirectory(rootOf, displayDirectory, signal);
|
|
262
|
-
const segments = normalizeDisplaySegments(displayDirectory);
|
|
263
|
-
if (segments === undefined) return undefined;
|
|
264
|
-
if (segments.length === 0) return root;
|
|
265
|
-
try {
|
|
266
|
-
const result = await runnerFor(rootCoords.distro).exec({
|
|
267
|
-
cwd: "/",
|
|
268
|
-
argv: resolveDirectoryArgv(rootLinux, segments),
|
|
269
|
-
maxOutputBytes: 4_000_000,
|
|
270
|
-
timeoutMs: 30_000,
|
|
271
|
-
signal,
|
|
272
|
-
});
|
|
273
|
-
signal.throwIfAborted();
|
|
274
|
-
if (result.exitCode !== 0) return undefined;
|
|
275
|
-
return posixToUnc(rootCoords.distro, result.stdout.trim());
|
|
276
|
-
} catch {
|
|
221
|
+
if (!isWslUnc(root))
|
|
222
|
+
return defaultTraversal;
|
|
223
|
+
const rootCoords = uncToPosix(root);
|
|
224
|
+
if (!rootCoords)
|
|
225
|
+
return defaultTraversal;
|
|
226
|
+
const rootLinux = rootCoords.linuxPath;
|
|
227
|
+
/**
|
|
228
|
+
* List one distro directory through the resident agent's guarded `find`.
|
|
229
|
+
*
|
|
230
|
+
* @param absolute - the UNC directory to list.
|
|
231
|
+
* @param signal - the caller's cancellation.
|
|
232
|
+
* @param onRefusal - the refusal policy: rethrow the error, or degrade to
|
|
233
|
+
* an empty listing.
|
|
234
|
+
* @returns the parsed entries.
|
|
235
|
+
*/
|
|
236
|
+
const listDistro = async (absolute, signal, onRefusal) => {
|
|
277
237
|
signal.throwIfAborted();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
238
|
+
const coords = uncToPosix(absolute);
|
|
239
|
+
if (!coords)
|
|
240
|
+
return onRefusal(new Error(`not a distro path: ${absolute}`));
|
|
241
|
+
const { linuxPath } = coords;
|
|
242
|
+
try {
|
|
243
|
+
const result = await runnerFor(rootCoords.distro).exec({
|
|
244
|
+
cwd: "/",
|
|
245
|
+
argv: traversalArgv(rootLinux, linuxPath, maxEntries),
|
|
246
|
+
maxOutputBytes: 4_000_000,
|
|
247
|
+
timeoutMs: 30_000,
|
|
248
|
+
signal,
|
|
249
|
+
});
|
|
250
|
+
signal.throwIfAborted();
|
|
251
|
+
if (result.exitCode !== 0) {
|
|
252
|
+
return onRefusal(new Error(`listing "${linuxPath}" failed: ${String(result.stderr).trim() || `exit ${result.exitCode}`}`));
|
|
253
|
+
}
|
|
254
|
+
return parseTraversalListing(result.stdout);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
signal.throwIfAborted();
|
|
258
|
+
return onRefusal(error);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
readWorkspaceRoot(absolute, signal) {
|
|
263
|
+
if (!isWslUnc(absolute))
|
|
264
|
+
return defaultTraversal.readWorkspaceRoot(absolute, signal);
|
|
265
|
+
return listDistro(absolute, signal, (error) => {
|
|
266
|
+
throw error;
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
readDirectory(absolute, signal) {
|
|
270
|
+
if (!isWslUnc(absolute))
|
|
271
|
+
return defaultTraversal.readDirectory(absolute, signal);
|
|
272
|
+
return listDistro(absolute, signal, () => []);
|
|
273
|
+
},
|
|
274
|
+
async resolveDisplayDirectory(rootOf, displayDirectory, signal) {
|
|
275
|
+
signal.throwIfAborted();
|
|
276
|
+
if (!isWslUnc(rootOf))
|
|
277
|
+
return defaultTraversal.resolveDisplayDirectory(rootOf, displayDirectory, signal);
|
|
278
|
+
const segments = normalizeDisplaySegments(displayDirectory);
|
|
279
|
+
if (segments === undefined)
|
|
280
|
+
return undefined;
|
|
281
|
+
if (segments.length === 0)
|
|
282
|
+
return root;
|
|
283
|
+
try {
|
|
284
|
+
const result = await runnerFor(rootCoords.distro).exec({
|
|
285
|
+
cwd: "/",
|
|
286
|
+
argv: resolveDirectoryArgv(rootLinux, segments),
|
|
287
|
+
maxOutputBytes: 4_000_000,
|
|
288
|
+
timeoutMs: 30_000,
|
|
289
|
+
signal,
|
|
290
|
+
});
|
|
291
|
+
signal.throwIfAborted();
|
|
292
|
+
if (result.exitCode !== 0)
|
|
293
|
+
return undefined;
|
|
294
|
+
return posixToUnc(rootCoords.distro, String(result.stdout).trim());
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
signal.throwIfAborted();
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
};
|
|
282
302
|
}
|
|
283
|
-
|
|
284
303
|
export default wslFileReferenceTraversal;
|