dsh-plugin-wsl-env 0.7.5 → 0.9.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +345 -8
  2. package/CONTRIBUTING.md +221 -68
  3. package/README.md +50 -35
  4. package/SECURITY.md +18 -2
  5. package/SUPPORT.md +23 -7
  6. package/cordis.patch.yml +9 -0
  7. package/docs/ARCHITECTURE.md +200 -57
  8. package/docs/CONFIGURATION.md +180 -16
  9. package/docs/LIMITATIONS.md +91 -33
  10. package/docs/PARITY.md +66 -38
  11. package/docs/PEER-PARITY.md +95 -32
  12. package/docs/README.zh.md +49 -31
  13. package/docs/UPSTREAM-FSIO-EXPORT.md +159 -50
  14. package/docs/UPSTREAM-TERMINAL-TITLE.md +49 -14
  15. package/lib/agent-confined.d.ts +42 -0
  16. package/lib/agent-confined.js +25 -41
  17. package/lib/agent-errors.d.ts +31 -0
  18. package/lib/agent-errors.js +19 -16
  19. package/lib/agent-exec.d.ts +159 -0
  20. package/lib/agent-exec.js +151 -165
  21. package/lib/agent-protocol.d.ts +192 -0
  22. package/lib/agent-protocol.js +76 -100
  23. package/lib/agent-shared.d.ts +27 -0
  24. package/lib/agent-shared.js +12 -12
  25. package/lib/agent.d.ts +353 -0
  26. package/lib/agent.js +536 -552
  27. package/lib/auto-preset.d.ts +110 -0
  28. package/lib/auto-preset.js +157 -141
  29. package/lib/bwrap.d.ts +90 -0
  30. package/lib/bwrap.js +18 -18
  31. package/lib/file-reference-wsl.d.ts +160 -0
  32. package/lib/file-reference-wsl.js +192 -173
  33. package/lib/fs-decisions.d.ts +107 -0
  34. package/lib/fs-decisions.js +64 -60
  35. package/lib/fs-routing.d.ts +182 -0
  36. package/lib/fs-routing.js +173 -116
  37. package/lib/fs-substrate.d.ts +258 -0
  38. package/lib/fs-substrate.js +248 -248
  39. package/lib/fs.d.ts +10 -0
  40. package/lib/fs.js +5 -0
  41. package/lib/fsio-agent.d.ts +307 -0
  42. package/lib/fsio-agent.js +414 -410
  43. package/lib/fsio-text.d.ts +115 -0
  44. package/lib/fsio-text.js +104 -84
  45. package/lib/git-route.d.ts +100 -0
  46. package/lib/git-route.js +46 -47
  47. package/lib/index.d.ts +1003 -0
  48. package/lib/index.js +1106 -1003
  49. package/lib/listing.d.ts +114 -0
  50. package/lib/listing.js +68 -55
  51. package/lib/paths.d.ts +182 -0
  52. package/lib/paths.js +134 -109
  53. package/lib/picker.d.ts +266 -0
  54. package/lib/picker.js +371 -351
  55. package/lib/ports.d.ts +44 -0
  56. package/lib/ports.js +39 -29
  57. package/lib/preflight.d.ts +91 -0
  58. package/lib/preflight.js +94 -0
  59. package/lib/preset-choice.d.ts +33 -0
  60. package/lib/preset-choice.js +15 -7
  61. package/lib/probe-cache.d.ts +51 -0
  62. package/lib/probe-cache.js +43 -29
  63. package/lib/sandbox-core.d.ts +149 -0
  64. package/lib/sandbox-core.js +239 -87
  65. package/lib/sandbox.d.ts +48 -0
  66. package/lib/sandbox.js +4 -4
  67. package/lib/search-exec.d.ts +96 -0
  68. package/lib/search-exec.js +52 -79
  69. package/lib/search-route.d.ts +75 -0
  70. package/lib/search-route.js +30 -31
  71. package/lib/shell-env.d.ts +88 -0
  72. package/lib/shell-env.js +78 -74
  73. package/lib/shell.d.ts +10 -0
  74. package/lib/shell.js +5 -0
  75. package/lib/subprocess.d.ts +313 -0
  76. package/lib/subprocess.js +267 -262
  77. package/lib/terminal-activity.d.ts +98 -0
  78. package/lib/terminal-activity.js +29 -39
  79. package/lib/terminal-route.d.ts +27 -0
  80. package/lib/terminal-route.js +16 -8
  81. package/lib/watcher.d.ts +77 -0
  82. package/lib/watcher.js +120 -126
  83. package/lib/workspace-files-route.d.ts +132 -0
  84. package/lib/workspace-files-route.js +73 -81
  85. package/lib/workspace-files-wsl.d.ts +257 -0
  86. package/lib/workspace-files-wsl.js +311 -251
  87. package/lib/wsl.d.ts +266 -0
  88. package/lib/wsl.js +150 -155
  89. package/locale/preflight/en.json +6 -0
  90. package/locale/preflight/zh.json +6 -0
  91. package/package.json +78 -22
  92. package/scripts/diagnose.sh +92 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The text mechanics of the distro-side filesystem, replicated from
3
+ * `@deepseek-ai/dsh-fs-local`'s fsio module so `lib/fsio-agent.js` can run the
4
+ * same orchestration without importing the peer.
5
+ *
6
+ * Everything here is pure and validation-only: no I/O, no Node fs. The
7
+ * replication is deliberate and temporary — once the upstream package exports
8
+ * its fsio module (the export is proposed in docs/UPSTREAM-FSIO-EXPORT.md),
9
+ * these functions are deleted in favour of the imported originals. Until then
10
+ * they are kept byte-for-byte faithful, including error messages, because the
11
+ * tool layer matches on both the `code` and what the model reads — and that
12
+ * faithfulness is verified, not assumed: docs/PEER-PARITY.md records the
13
+ * function-by-function comparison against the pinned peer's shipped code.
14
+ *
15
+ * This is a TypeScript source built to `lib/fsio-text.js`; edit THIS file and
16
+ * run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
17
+ * fails when it drifts.
18
+ *
19
+ * @module dsh-plugin-wsl/fsio-text
20
+ */
21
+ /** The byte prefix scanned for a NUL before a read is declared binary. */
22
+ export declare const BINARY_SAMPLE_BYTES = 8192;
23
+ /**
24
+ * A coded filesystem failure. `code` carries the `FS_*` token the provider
25
+ * maps onto the peer's `FsError`; the class stays local so this module imports
26
+ * no peer and stays unit-testable in a bare checkout.
27
+ */
28
+ export declare class FsCodedError extends Error {
29
+ /** The `FS_*` token, for callers that must not match on the message. */
30
+ code: string;
31
+ /**
32
+ * @param message - human-readable, model-facing.
33
+ * @param code - the `FS_*` token.
34
+ * @param cause - the underlying failure, when there is one.
35
+ */
36
+ constructor(message: string, code: string, cause?: Error);
37
+ }
38
+ /**
39
+ * The structured not-text failure.
40
+ *
41
+ * @param verb - the operation being attempted, for the message.
42
+ * @param displayPath - the caller-facing path.
43
+ * @returns the refusal every decode failure raises.
44
+ */
45
+ export declare function notTextError(verb: string, displayPath: string): FsCodedError;
46
+ /**
47
+ * Decode a buffer as strict UTF-8, rejecting invalid byte sequences the way
48
+ * the peer's fsio does.
49
+ *
50
+ * @param buffer - the bytes to decode.
51
+ * @param verb - the operation being attempted, for the message.
52
+ * @param displayPath - the caller-facing path.
53
+ * @returns the decoded text.
54
+ * @throws {FsCodedError} with `FS_NOT_TEXT` on invalid UTF-8.
55
+ */
56
+ export declare function decodeUtf8(buffer: Buffer, verb: string, displayPath: string): string;
57
+ /**
58
+ * Decode one streamed chunk, carrying multi-byte sequences across chunks.
59
+ *
60
+ * @param decoder - the streaming decoder owned by the caller.
61
+ * @param chunk - the next chunk, or `undefined` to flush.
62
+ * @param verb - the operation being attempted, for the message.
63
+ * @param displayPath - the caller-facing path.
64
+ * @returns the decoded text.
65
+ * @throws {FsCodedError} with `FS_NOT_TEXT` on invalid UTF-8.
66
+ */
67
+ export declare function decodeUtf8Stream(decoder: import("node:util").TextDecoder, chunk: Buffer | undefined, verb: string, displayPath: string): string;
68
+ /**
69
+ * Collapse CRLF to LF — the canonical in-memory form every edit/diff basis uses.
70
+ *
71
+ * @param content - the text to normalize.
72
+ * @returns the text with every CRLF collapsed to LF.
73
+ */
74
+ export declare function normalizeLineEndings(content: string): string;
75
+ /**
76
+ * The line-ending style a file was read with.
77
+ *
78
+ * @param raw - decoded text in whatever line-ending style the file had.
79
+ * @returns the dominant style of the first 4096 bytes.
80
+ */
81
+ export declare function detectLineEndings(raw: string): "LF" | "CRLF";
82
+ /**
83
+ * Convert LF-normalized content back to the style detected at read time.
84
+ *
85
+ * @param content - the LF-normalized (edited) text.
86
+ * @param lineEndings - the original file's style.
87
+ * @returns the content in the original file's line-ending style.
88
+ */
89
+ export declare function restoreLineEndings(content: string, lineEndings: "LF" | "CRLF"): string;
90
+ /**
91
+ * Apply a literal replacement to LF-normalized content — the peer's edit
92
+ * algorithm, replicated: CRLF inside the needle is normalized before
93
+ * matching, an empty needle is refused, and a multi-match needle is ambiguous
94
+ * unless `replaceAll` is set.
95
+ *
96
+ * @param content - the current file content, already LF-normalized.
97
+ * @param oldString - literal text to find.
98
+ * @param newString - literal replacement text.
99
+ * @param replaceAll - replace every match instead of requiring one.
100
+ * @param displayPath - the caller-facing path for error messages.
101
+ * @returns the edited text and count.
102
+ * @throws {FsCodedError} with `FS_EDIT_NOT_FOUND` or `FS_AMBIGUOUS_EDIT`.
103
+ */
104
+ export declare function applyLiteralEdit(content: string, oldString: string, newString: string, replaceAll: boolean | undefined, displayPath: string): {
105
+ content: string;
106
+ replacements: number;
107
+ };
108
+ /**
109
+ * Abort boundary mirroring the peer's `throwIfAborted`.
110
+ *
111
+ * @param signal - the caller's signal.
112
+ * @param verb - the operation being attempted, for the message.
113
+ * @throws {FsCodedError} with `FS_ABORTED` when the signal is done.
114
+ */
115
+ export declare function throwIfAborted(signal: AbortSignal | undefined, verb: string): void;
package/lib/fsio-text.js CHANGED
@@ -12,142 +12,162 @@
12
12
  * faithfulness is verified, not assumed: docs/PEER-PARITY.md records the
13
13
  * function-by-function comparison against the pinned peer's shipped code.
14
14
  *
15
+ * This is a TypeScript source built to `lib/fsio-text.js`; edit THIS file and
16
+ * run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
17
+ * fails when it drifts.
18
+ *
15
19
  * @module dsh-plugin-wsl/fsio-text
16
20
  */
17
-
18
21
  /** The byte prefix scanned for a NUL before a read is declared binary. */
19
22
  export const BINARY_SAMPLE_BYTES = 8192;
20
-
21
23
  /**
22
24
  * A coded filesystem failure. `code` carries the `FS_*` token the provider
23
25
  * maps onto the peer's `FsError`; the class stays local so this module imports
24
26
  * no peer and stays unit-testable in a bare checkout.
25
27
  */
26
28
  export class FsCodedError extends Error {
27
- /**
28
- * @param {string} message - human-readable, model-facing.
29
- * @param {string} code - the `FS_*` token.
30
- * @param {Error} [cause] - the underlying failure, when there is one.
31
- */
32
- constructor(message, code, cause) {
33
- super(message, cause === undefined ? {} : { cause });
34
- this.name = "FsCodedError";
35
- this.code = code;
36
- }
29
+ /**
30
+ * @param message - human-readable, model-facing.
31
+ * @param code - the `FS_*` token.
32
+ * @param cause - the underlying failure, when there is one.
33
+ */
34
+ constructor(message, code, cause) {
35
+ super(message, cause === undefined ? {} : { cause });
36
+ this.name = "FsCodedError";
37
+ this.code = code;
38
+ }
37
39
  }
38
-
39
40
  /**
40
- * @param {string} verb - the operation being attempted, for the message.
41
- * @param {string} displayPath - the caller-facing path.
42
- * @returns {FsCodedError} the structured not-text failure.
41
+ * The structured not-text failure.
42
+ *
43
+ * @param verb - the operation being attempted, for the message.
44
+ * @param displayPath - the caller-facing path.
45
+ * @returns the refusal every decode failure raises.
43
46
  */
44
47
  export function notTextError(verb, displayPath) {
45
- return new FsCodedError(`cannot ${verb} "${displayPath}": invalid UTF-8 text`, "FS_NOT_TEXT");
48
+ return new FsCodedError(`cannot ${verb} "${displayPath}": invalid UTF-8 text`, "FS_NOT_TEXT");
46
49
  }
47
-
48
50
  /**
49
51
  * Decode a buffer as strict UTF-8, rejecting invalid byte sequences the way
50
52
  * the peer's fsio does.
51
- * @param {Buffer} buffer - the bytes to decode.
52
- * @param {string} verb - the operation being attempted, for the message.
53
- * @param {string} displayPath - the caller-facing path.
54
- * @returns {string} the decoded text.
53
+ *
54
+ * @param buffer - the bytes to decode.
55
+ * @param verb - the operation being attempted, for the message.
56
+ * @param displayPath - the caller-facing path.
57
+ * @returns the decoded text.
55
58
  * @throws {FsCodedError} with `FS_NOT_TEXT` on invalid UTF-8.
56
59
  */
57
60
  export function decodeUtf8(buffer, verb, displayPath) {
58
- try {
59
- return new TextDecoder("utf-8", { fatal: true }).decode(buffer);
60
- } catch (error) {
61
- if (!(error instanceof TypeError)) throw error;
62
- throw notTextError(verb, displayPath);
63
- }
61
+ try {
62
+ return new TextDecoder("utf-8", { fatal: true }).decode(buffer);
63
+ }
64
+ catch (error) {
65
+ if (!(error instanceof TypeError))
66
+ throw error;
67
+ throw notTextError(verb, displayPath);
68
+ }
64
69
  }
65
-
66
70
  /**
67
71
  * Decode one streamed chunk, carrying multi-byte sequences across chunks.
68
- * @param {TextDecoder} decoder - the streaming decoder owned by the caller.
69
- * @param {Buffer|undefined} chunk - the next chunk, or `undefined` to flush.
70
- * @param {string} verb - the operation being attempted, for the message.
71
- * @param {string} displayPath - the caller-facing path.
72
- * @returns {string} the decoded text.
72
+ *
73
+ * @param decoder - the streaming decoder owned by the caller.
74
+ * @param chunk - the next chunk, or `undefined` to flush.
75
+ * @param verb - the operation being attempted, for the message.
76
+ * @param displayPath - the caller-facing path.
77
+ * @returns the decoded text.
73
78
  * @throws {FsCodedError} with `FS_NOT_TEXT` on invalid UTF-8.
74
79
  */
75
80
  export function decodeUtf8Stream(decoder, chunk, verb, displayPath) {
76
- try {
77
- return chunk ? decoder.decode(chunk, { stream: true }) : decoder.decode();
78
- } catch (error) {
79
- if (!(error instanceof TypeError)) throw error;
80
- throw notTextError(verb, displayPath);
81
- }
81
+ try {
82
+ return chunk ? decoder.decode(chunk, { stream: true }) : decoder.decode();
83
+ }
84
+ catch (error) {
85
+ if (!(error instanceof TypeError))
86
+ throw error;
87
+ throw notTextError(verb, displayPath);
88
+ }
82
89
  }
83
-
84
- /** Collapse CRLF to LF — the canonical in-memory form every edit/diff basis uses. */
90
+ /**
91
+ * Collapse CRLF to LF — the canonical in-memory form every edit/diff basis uses.
92
+ *
93
+ * @param content - the text to normalize.
94
+ * @returns the text with every CRLF collapsed to LF.
95
+ */
85
96
  export function normalizeLineEndings(content) {
86
- return content.replaceAll("\r\n", "\n");
97
+ return content.replaceAll("\r\n", "\n");
87
98
  }
88
-
89
- /** @param {string} raw - decoded text in whatever line-ending style the file had. */
99
+ /**
100
+ * The line-ending style a file was read with.
101
+ *
102
+ * @param raw - decoded text in whatever line-ending style the file had.
103
+ * @returns the dominant style of the first 4096 bytes.
104
+ */
90
105
  export function detectLineEndings(raw) {
91
- const sample = raw.slice(0, 4096);
92
- const crlfCount = sample.split("\r\n").length - 1;
93
- return crlfCount > sample.split("\n").length - 1 - crlfCount ? "CRLF" : "LF";
106
+ const sample = raw.slice(0, 4096);
107
+ const crlfCount = sample.split("\r\n").length - 1;
108
+ return crlfCount > sample.split("\n").length - 1 - crlfCount ? "CRLF" : "LF";
94
109
  }
95
-
96
110
  /**
97
111
  * Convert LF-normalized content back to the style detected at read time.
98
- * @param {string} content - the LF-normalized (edited) text.
99
- * @param {"LF"|"CRLF"} lineEndings - the original file's style.
100
- * @returns {string} the content in the original file's line-ending style.
112
+ *
113
+ * @param content - the LF-normalized (edited) text.
114
+ * @param lineEndings - the original file's style.
115
+ * @returns the content in the original file's line-ending style.
101
116
  */
102
117
  export function restoreLineEndings(content, lineEndings) {
103
- if (lineEndings === "LF") return content;
104
- return normalizeLineEndings(content).replaceAll("\n", "\r\n");
118
+ if (lineEndings === "LF")
119
+ return content;
120
+ return normalizeLineEndings(content).replaceAll("\n", "\r\n");
105
121
  }
106
-
122
+ /**
123
+ * Count how many times `search` appears in `content`.
124
+ *
125
+ * @param content - the haystack.
126
+ * @param search - the needle.
127
+ * @returns the occurrence count.
128
+ */
107
129
  function countOccurrences(content, search) {
108
- return content.split(search).length - 1;
130
+ return content.split(search).length - 1;
109
131
  }
110
-
111
132
  /**
112
133
  * Apply a literal replacement to LF-normalized content — the peer's edit
113
134
  * algorithm, replicated: CRLF inside the needle is normalized before
114
135
  * matching, an empty needle is refused, and a multi-match needle is ambiguous
115
136
  * unless `replaceAll` is set.
116
137
  *
117
- * @param {string} content - the current file content, already LF-normalized.
118
- * @param {string} oldString - literal text to find.
119
- * @param {string} newString - literal replacement text.
120
- * @param {boolean} replaceAll - replace every match instead of requiring one.
121
- * @param {string} displayPath - the caller-facing path for error messages.
122
- * @returns {{content: string, replacements: number}} the edited text and count.
138
+ * @param content - the current file content, already LF-normalized.
139
+ * @param oldString - literal text to find.
140
+ * @param newString - literal replacement text.
141
+ * @param replaceAll - replace every match instead of requiring one.
142
+ * @param displayPath - the caller-facing path for error messages.
143
+ * @returns the edited text and count.
123
144
  * @throws {FsCodedError} with `FS_EDIT_NOT_FOUND` or `FS_AMBIGUOUS_EDIT`.
124
145
  */
125
146
  export function applyLiteralEdit(content, oldString, newString, replaceAll, displayPath) {
126
- const oldNorm = normalizeLineEndings(oldString);
127
- if (oldNorm.length === 0) throw new FsCodedError("old_string must be a non-empty string", "FS_EDIT_NOT_FOUND");
128
- const newNorm = normalizeLineEndings(newString);
129
- const replacements = countOccurrences(content, oldNorm);
130
- if (replacements === 0) throw new FsCodedError(`old_string was not found in "${displayPath}"`, "FS_EDIT_NOT_FOUND");
131
- if (!replaceAll && replacements > 1) {
132
- throw new FsCodedError(
133
- `old_string matched ${replacements} times in "${displayPath}"; provide a more specific old_string or set replace_all to true`,
134
- "FS_AMBIGUOUS_EDIT",
135
- );
136
- }
137
- return {
138
- content: content.split(oldNorm).join(newNorm),
139
- replacements,
140
- };
147
+ const oldNorm = normalizeLineEndings(oldString);
148
+ if (oldNorm.length === 0)
149
+ throw new FsCodedError("old_string must be a non-empty string", "FS_EDIT_NOT_FOUND");
150
+ const newNorm = normalizeLineEndings(newString);
151
+ const replacements = countOccurrences(content, oldNorm);
152
+ if (replacements === 0)
153
+ throw new FsCodedError(`old_string was not found in "${displayPath}"`, "FS_EDIT_NOT_FOUND");
154
+ if (!replaceAll && replacements > 1) {
155
+ throw new FsCodedError(`old_string matched ${replacements} times in "${displayPath}"; provide a more specific old_string or set replace_all to true`, "FS_AMBIGUOUS_EDIT");
156
+ }
157
+ return {
158
+ content: content.split(oldNorm).join(newNorm),
159
+ replacements,
160
+ };
141
161
  }
142
-
143
162
  /**
144
163
  * Abort boundary mirroring the peer's `throwIfAborted`.
145
- * @param {AbortSignal|undefined} signal - the caller's signal.
146
- * @param {string} verb - the operation being attempted, for the message.
164
+ *
165
+ * @param signal - the caller's signal.
166
+ * @param verb - the operation being attempted, for the message.
147
167
  * @throws {FsCodedError} with `FS_ABORTED` when the signal is done.
148
168
  */
149
169
  export function throwIfAborted(signal, verb) {
150
- if (signal?.aborted) {
151
- throw new FsCodedError(`${verb} aborted`, "FS_ABORTED", signal.reason instanceof Error ? signal.reason : undefined);
152
- }
170
+ if (signal?.aborted) {
171
+ throw new FsCodedError(`${verb} aborted`, "FS_ABORTED", signal.reason instanceof Error ? signal.reason : undefined);
172
+ }
153
173
  }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Which git spawns belong inside the distro, and the `wsl.exe` argv they
3
+ * become there.
4
+ *
5
+ * `workspace-changes` snapshots a workspace's git state by spawning the host
6
+ * git through `ctx.subprocess` with the workspace as the working directory.
7
+ * On a distro workspace that spawn runs Windows git against the 9P share —
8
+ * the same slow direction as the search path, and the snapshot's
9
+ * `git add --all` is a full work-tree scan, the most metadata-heavy operation
10
+ * there is. The interception point is the same `spawn` override; the decision
11
+ * lives here, pure and unit-testable, mirroring `search-route.js`.
12
+ *
13
+ * ## What is routed, and what deliberately is not
14
+ *
15
+ * A spawn is routed only when the working directory names the distro (a
16
+ * `\\wsl.localhost\<distro>` UNC) and the argv carries none of the discovery
17
+ * flags (`--show-toplevel`, `--absolute-git-dir`, `--git-path`). Those three
18
+ * print **absolute paths**, which `workspace-changes` resolves against the
19
+ * UNC workspace (`git.ts` `resolve(cwd, line)`) — a distro git would answer
20
+ * in Linux paths and corrupt that resolution. Host git answers in UNC form,
21
+ * which is exactly what the caller wants, and the discovery call is one
22
+ * cheap command rather than a work-tree scan. Everything else — `add`,
23
+ * `write-tree`, `ls-tree`, `cat-file`, `diff-tree`, `ls-files`,
24
+ * `check-ignore` — is safe to route: snapshot calls run in an environment
25
+ * fully isolated from the user's git state (`GIT_INDEX_FILE` /
26
+ * `GIT_OBJECT_DIRECTORY` / `GIT_ALTERNATE_OBJECT_DIRECTORIES` pointing at the
27
+ * tool's private scratch), so the distro git mutates scratch, never the
28
+ * repository.
29
+ *
30
+ * ## Environment translation
31
+ *
32
+ * The routed spawn must forward the caller's `GIT_*` environment into the
33
+ * distro, and its values are paths in Windows form (`\\wsl.localhost\...`
34
+ * identities and drive-letter scratch dirs). `translateGitEnv` rewrites those
35
+ * two shapes into distro paths with the same pure translations the fs seam
36
+ * uses, forwards every other value verbatim (`GIT_CONFIG_COUNT=0` and
37
+ * friends), and returns the names that must ride `WSLENV`. One documented
38
+ * limit: a colon-separated multi-directory value (none of the current
39
+ * callers set one) passes through untranslated.
40
+ *
41
+ * This is a TypeScript source built to `lib/git-route.js`; edit THIS file and
42
+ * run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
43
+ * fails when it drifts.
44
+ *
45
+ * @module dsh-plugin-wsl/git-route
46
+ */
47
+ /**
48
+ * Whether a spawn's program is git.
49
+ *
50
+ * @param program - the spawn's `argv[0]`.
51
+ * @returns true when the spawn is a git launch.
52
+ */
53
+ export declare function isGitProgram(program: unknown): boolean;
54
+ /**
55
+ * Translate one `GIT_*` value into the distro's coordinate system.
56
+ *
57
+ * UNC identities (`\\wsl.localhost\<distro>\...`) map onto their Linux paths;
58
+ * drive-letter paths map onto their `/mnt/<drive>` mounts; anything else is
59
+ * not a path we can name and passes through verbatim.
60
+ *
61
+ * @param value - the env value.
62
+ * @returns the distro-side spelling of the value.
63
+ */
64
+ export declare function translateGitEnvValue(value: string | undefined): string | undefined;
65
+ /**
66
+ * Translate a git spawn's environment for the distro and name the keys that
67
+ * must ride `WSLENV`.
68
+ *
69
+ * @param env - the spawn's environment, or undefined.
70
+ * @returns the environment to forward (every key, path-shaped values
71
+ * translated) and the `WSLENV` names.
72
+ */
73
+ export declare function translateGitEnv(env: Record<string, string | undefined> | undefined): {
74
+ env: Record<string, string | undefined>;
75
+ wslenv: string[];
76
+ };
77
+ /**
78
+ * The distro-side rewrite of one git spawn, when it belongs in the distro.
79
+ *
80
+ * `gitArgv` is the distro command itself; `wslArgv` is the same command
81
+ * wrapped for a one-shot `wsl.exe --exec` launch — git snapshot operations
82
+ * are not latency-sensitive, so the routing stays on the simple transport
83
+ * and does not need the resident agent. `wsl.exe` itself must start in a
84
+ * Windows directory, so the caller supplies its own host cwd.
85
+ *
86
+ * @param object - the spawn's inputs.
87
+ * @returns the rewrite, or undefined when the spawn is not a routable distro
88
+ * git launch (non-git programs, host directories, and the absolute-output
89
+ * discovery flags all keep the shipped spawn).
90
+ */
91
+ export declare function gitSpawnRewrite({ argv, cwd, wslPath }: {
92
+ argv?: readonly string[];
93
+ cwd?: string;
94
+ wslPath: string;
95
+ }): {
96
+ distro: string;
97
+ linuxCwd: string;
98
+ gitArgv: string[];
99
+ wslArgv: string[];
100
+ } | undefined;
package/lib/git-route.js CHANGED
@@ -38,38 +38,39 @@
38
38
  * limit: a colon-separated multi-directory value (none of the current
39
39
  * callers set one) passes through untranslated.
40
40
  *
41
+ * This is a TypeScript source built to `lib/git-route.js`; edit THIS file and
42
+ * run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
43
+ * fails when it drifts.
44
+ *
41
45
  * @module dsh-plugin-wsl/git-route
42
46
  */
43
-
44
47
  import { isWslUnc, uncToPosix, windowsToLinuxMount } from "./paths.js";
45
-
46
48
  /** The one program this rewrite passes through to the distro. */
47
49
  const GIT_PROGRAM = "git";
48
-
49
50
  /** Discovery flags print absolute paths — host git keeps that contract. */
50
51
  const DISCOVERY_FLAGS = ["--show-toplevel", "--absolute-git-dir", "--git-path"];
51
-
52
52
  /**
53
53
  * Whether a spawn's program is git.
54
- * @param unknown - the spawn's `argv[0]`.
54
+ *
55
+ * @param program - the spawn's `argv[0]`.
55
56
  * @returns true when the spawn is a git launch.
56
57
  */
57
58
  export function isGitProgram(program) {
58
- if (typeof program !== "string" || program.length === 0) return false;
59
- const base = program.slice(Math.max(program.lastIndexOf("/"), program.lastIndexOf("\\")) + 1);
60
- return base.toLowerCase().replace(/\.exe$/u, "") === GIT_PROGRAM;
59
+ if (typeof program !== "string" || program.length === 0)
60
+ return false;
61
+ const base = program.slice(Math.max(program.lastIndexOf("/"), program.lastIndexOf("\\")) + 1);
62
+ return base.toLowerCase().replace(/\.exe$/u, "") === GIT_PROGRAM;
61
63
  }
62
-
63
64
  /**
64
65
  * Whether a git argv carries the discovery flags whose absolute output the
65
66
  * caller resolves against the UNC world.
66
- * @param {unknown[]} argv - the spawn's argv; `argv[0]` is the program.
67
+ *
68
+ * @param argv - the spawn's argv; `argv[0]` is the program.
67
69
  * @returns true when the spawn must stay on host git.
68
70
  */
69
71
  function namesDiscoveryPaths(argv) {
70
- return argv.some((arg) => typeof arg === "string" && DISCOVERY_FLAGS.includes(arg));
72
+ return argv.some((arg) => typeof arg === "string" && DISCOVERY_FLAGS.includes(arg));
71
73
  }
72
-
73
74
  /**
74
75
  * Translate one `GIT_*` value into the distro's coordinate system.
75
76
  *
@@ -81,15 +82,15 @@ function namesDiscoveryPaths(argv) {
81
82
  * @returns the distro-side spelling of the value.
82
83
  */
83
84
  export function translateGitEnvValue(value) {
84
- if (typeof value !== "string") return value;
85
- if (isWslUnc(value)) {
86
- const parsed = uncToPosix(value);
87
- return parsed === undefined ? value : parsed.linuxPath.length > 0 ? parsed.linuxPath : "/";
88
- }
89
- const mounted = windowsToLinuxMount(value);
90
- return mounted === undefined ? value : mounted;
85
+ if (typeof value !== "string")
86
+ return value;
87
+ if (isWslUnc(value)) {
88
+ const parsed = uncToPosix(value);
89
+ return parsed === undefined ? value : parsed.linuxPath.length > 0 ? parsed.linuxPath : "/";
90
+ }
91
+ const mounted = windowsToLinuxMount(value);
92
+ return mounted === undefined ? value : mounted;
91
93
  }
92
-
93
94
  /**
94
95
  * Translate a git spawn's environment for the distro and name the keys that
95
96
  * must ride `WSLENV`.
@@ -99,15 +100,14 @@ export function translateGitEnvValue(value) {
99
100
  * translated) and the `WSLENV` names.
100
101
  */
101
102
  export function translateGitEnv(env) {
102
- const translated = {};
103
- const names = [];
104
- for (const [name, value] of Object.entries(env ?? {})) {
105
- translated[name] = name.startsWith("GIT_") ? translateGitEnvValue(value) : value;
106
- names.push(name);
107
- }
108
- return { env: translated, wslenv: names };
103
+ const translated = {};
104
+ const names = [];
105
+ for (const [name, value] of Object.entries(env ?? {})) {
106
+ translated[name] = name.startsWith("GIT_") ? translateGitEnvValue(value) : value;
107
+ names.push(name);
108
+ }
109
+ return { env: translated, wslenv: names };
109
110
  }
110
-
111
111
  /**
112
112
  * The distro-side rewrite of one git spawn, when it belongs in the distro.
113
113
  *
@@ -118,25 +118,24 @@ export function translateGitEnv(env) {
118
118
  * Windows directory, so the caller supplies its own host cwd.
119
119
  *
120
120
  * @param object - the spawn's inputs.
121
- * @param {unknown[]} object.argv - the spawn's argv; `argv[0]` is the program.
122
- * @param {unknown} object.cwd - the spawn's working directory, any coordinate system.
123
- * @param {string} object.wslPath - path to `wsl.exe`.
124
- * @returns {{distro: string, linuxCwd: string, gitArgv: string[], wslArgv: string[]}|undefined} the
125
- * rewrite, or undefined when the spawn is not a routable distro git launch
126
- * (non-git programs, host directories, and the absolute-output discovery
127
- * flags all keep the shipped spawn).
121
+ * @returns the rewrite, or undefined when the spawn is not a routable distro
122
+ * git launch (non-git programs, host directories, and the absolute-output
123
+ * discovery flags all keep the shipped spawn).
128
124
  */
129
125
  export function gitSpawnRewrite({ argv, cwd, wslPath }) {
130
- if (!Array.isArray(argv) || !isGitProgram(argv[0])) return undefined;
131
- if (namesDiscoveryPaths(argv)) return undefined;
132
- const named = uncToPosix(cwd);
133
- if (named === undefined) return undefined;
134
- const linuxCwd = named.linuxPath.length > 0 ? named.linuxPath : "/";
135
- const gitArgv = [GIT_PROGRAM, ...argv.slice(1)];
136
- return {
137
- distro: named.distro,
138
- linuxCwd,
139
- gitArgv,
140
- wslArgv: [wslPath, "-d", named.distro, "--cd", linuxCwd, "--exec", ...gitArgv],
141
- };
126
+ if (!Array.isArray(argv) || !isGitProgram(argv[0]))
127
+ return undefined;
128
+ if (namesDiscoveryPaths(argv))
129
+ return undefined;
130
+ const named = uncToPosix(cwd);
131
+ if (named === undefined)
132
+ return undefined;
133
+ const linuxCwd = named.linuxPath.length > 0 ? named.linuxPath : "/";
134
+ const gitArgv = [GIT_PROGRAM, ...argv.slice(1)];
135
+ return {
136
+ distro: named.distro,
137
+ linuxCwd,
138
+ gitArgv,
139
+ wslArgv: [wslPath, "-d", named.distro, "--cd", linuxCwd, "--exec", ...gitArgv],
140
+ };
142
141
  }