@cleocode/paths 2026.9.20 → 2026.9.22

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.
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Cross-platform PATH composition and executable lookup.
3
+ *
4
+ * `PATH` is `:`-delimited on POSIX and `;`-delimited on Windows, where the
5
+ * variable is also commonly spelled `Path` and a bare command name resolves
6
+ * through `PATHEXT` (`git` → `git.exe`, `cleo` → `cleo.cmd`). Building PATH as
7
+ * `${dir}:${PATH}` on Windows fuses the new directory with the first real
8
+ * entry, and splitting on `:` cuts every `C:\…` drive-letter entry in half, so
9
+ * each helper here takes the platform explicitly (defaulting to the running
10
+ * one) and joins with that platform's path module. Tests inject `win32` on
11
+ * any host.
12
+ *
13
+ * Lookups are in-process filesystem probes — no `which`/`where` child — so
14
+ * they behave identically on every OS and cost no process spawn.
15
+ *
16
+ * @task T12605
17
+ */
18
+ /** Platform-injection options shared by the PATH helpers. */
19
+ export interface ExecPathOptions {
20
+ /** Platform whose PATH rules apply. @defaultValue `process.platform` */
21
+ platform?: NodeJS.Platform;
22
+ /** Environment holding `PATH`/`Path` and `PATHEXT`. @defaultValue `process.env` */
23
+ env?: NodeJS.ProcessEnv;
24
+ /**
25
+ * Predicate deciding whether a candidate file is runnable.
26
+ * @defaultValue a regular file that is executable (POSIX) or exists (win32)
27
+ */
28
+ isExecutable?: (candidate: string) => boolean;
29
+ }
30
+ /**
31
+ * Return the PATH list delimiter for a platform (`;` on win32, `:` elsewhere).
32
+ *
33
+ * @param platform - Target platform. @defaultValue `process.platform`
34
+ * @returns The delimiter character.
35
+ */
36
+ export declare function pathDelimiterFor(platform?: NodeJS.Platform): string;
37
+ /**
38
+ * Return the environment key that holds the executable search path.
39
+ *
40
+ * Windows environment names are case-insensitive and the system spelling is
41
+ * `Path`; writing a second `PATH` key beside it leaves the child with two
42
+ * conflicting values. The existing key is reused whatever its case.
43
+ *
44
+ * @param env - Environment to inspect.
45
+ * @param platform - Target platform. @defaultValue `process.platform`
46
+ * @returns `PATH`, or the existing case-variant on win32.
47
+ */
48
+ export declare function pathEnvKey(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
49
+ /**
50
+ * Split a PATH value into its non-empty directory entries.
51
+ *
52
+ * @param value - Raw PATH value (may be undefined).
53
+ * @param platform - Target platform. @defaultValue `process.platform`
54
+ * @returns Directory entries in search order.
55
+ * @example
56
+ * ```ts
57
+ * splitPathEnv('C:\\Windows;C:\\Git\\cmd', 'win32'); // ['C:\\Windows', 'C:\\Git\\cmd']
58
+ * ```
59
+ */
60
+ export declare function splitPathEnv(value: string | undefined, platform?: NodeJS.Platform): string[];
61
+ /**
62
+ * Prepend a directory to a PATH value using the platform's delimiter.
63
+ *
64
+ * @param dir - Directory to search first.
65
+ * @param currentPath - Existing PATH value (may be empty or undefined).
66
+ * @param platform - Target platform. @defaultValue `process.platform`
67
+ * @returns The composed PATH value; `dir` alone when `currentPath` is empty.
68
+ * @example
69
+ * ```ts
70
+ * prependPathEntry('C:\\shim', 'C:\\Windows', 'win32'); // 'C:\\shim;C:\\Windows'
71
+ * ```
72
+ */
73
+ export declare function prependPathEntry(dir: string, currentPath: string | undefined, platform?: NodeJS.Platform): string;
74
+ /**
75
+ * List the file names a bare command can resolve to on a platform.
76
+ *
77
+ * On win32 each `PATHEXT` extension is tried, and the bare name only when it
78
+ * already carries one of those extensions. Elsewhere the name is used as-is.
79
+ *
80
+ * @param name - Command name, e.g. `git`.
81
+ * @param opts - Platform and environment overrides.
82
+ * @returns Candidate file names in resolution order.
83
+ */
84
+ export declare function executableNames(name: string, opts?: ExecPathOptions): string[];
85
+ /**
86
+ * Resolve a command to the absolute path the OS would run, or `null`.
87
+ *
88
+ * Honours the platform's PATH delimiter, the win32 `Path` spelling, and
89
+ * `PATHEXT`. A name containing a path separator is checked directly rather
90
+ * than searched for.
91
+ *
92
+ * @param name - Command name or path.
93
+ * @param opts - Platform, environment and executability overrides.
94
+ * @returns Absolute path of the first match, or `null`.
95
+ * @example
96
+ * ```ts
97
+ * findOnPath('git'); // '/usr/bin/git' on Linux, 'C:\\Program Files\\Git\\cmd\\git.exe' on Windows
98
+ * ```
99
+ */
100
+ export declare function findOnPath(name: string, opts?: ExecPathOptions): string | null;
101
+ /** A shell command line resolved to an argv the platform can spawn. */
102
+ export interface ShellInvocation {
103
+ /** Shell executable. */
104
+ file: string;
105
+ /** Arguments that make the shell run the command line once. */
106
+ args: string[];
107
+ /** Pass to `child_process` so cmd.exe receives the quoted line untouched. */
108
+ windowsVerbatimArguments: boolean;
109
+ }
110
+ /**
111
+ * Resolve how to run a shell command line on a platform.
112
+ *
113
+ * `/bin/sh -c` on POSIX; on win32 `%ComSpec%` (cmd.exe) with `/d /s /c` and
114
+ * the line quoted verbatim — the same resolution Node applies for
115
+ * `shell: true`, made explicit so every caller that runs user-authored
116
+ * command strings (worktree hooks, release build commands) shares one
117
+ * resolver and tests can pin the platform.
118
+ *
119
+ * @param command - Command line to run.
120
+ * @param opts - Platform and environment overrides.
121
+ * @returns The executable, its argv and the verbatim-arguments flag.
122
+ * @example
123
+ * ```ts
124
+ * const sh = shellInvocation('pnpm build');
125
+ * execFile(sh.file, sh.args, { windowsVerbatimArguments: sh.windowsVerbatimArguments });
126
+ * ```
127
+ */
128
+ export declare function shellInvocation(command: string, opts?: ExecPathOptions): ShellInvocation;
129
+ /**
130
+ * Quote one argument for a `.cmd`/`.bat` target launched through cmd.exe.
131
+ *
132
+ * Two layers, as cmd parses the line and the batch file's program parses its
133
+ * argv: MSVCRT quoting (backslashes before a quote doubled, the quote
134
+ * escaped, the whole argument wrapped in quotes), then every cmd
135
+ * metacharacter caret-escaped TWICE — cmd re-parses the line when it
136
+ * dispatches a batch file, consuming one caret layer each pass. Because every
137
+ * quote is escaped, cmd never sees a quoted region in which operators such as
138
+ * `&` or `|` would be live. `%VAR%` expands before carets are processed, but
139
+ * `%NAME^%` names a variable (`NAME^`) that is never defined, and an undefined
140
+ * reference is left verbatim on a `cmd /c` line. This is the scheme `cross-spawn` uses against the BatBadBut class
141
+ * (CVE-2024-27980).
142
+ *
143
+ * @param arg - Literal argument.
144
+ * @returns The escaped token.
145
+ * @throws When `arg` holds CR or LF: cmd.exe ends the command at a line
146
+ * break and no escape can carry one into a batch file.
147
+ * @example
148
+ * ```ts
149
+ * quoteCmdArg('a b'); // '^^^"a^^^ b^^^"'
150
+ * ```
151
+ */
152
+ export declare function quoteCmdArg(arg: string): string;
153
+ /** An executable plus argv, resolved for `child_process.spawn` on a platform. */
154
+ export interface SpawnInvocation {
155
+ /** File to spawn. */
156
+ file: string;
157
+ /** Arguments to pass. */
158
+ args: string[];
159
+ /** Pass through to `spawn` — `true` only for the cmd.exe batch form. */
160
+ windowsVerbatimArguments: boolean;
161
+ }
162
+ /**
163
+ * Resolve how to spawn `command args` without a shell on any platform.
164
+ *
165
+ * POSIX: returned unchanged. win32: the command is resolved to the absolute
166
+ * path {@link findOnPath} finds, because Node's own lookup tries only `.exe`
167
+ * and `.com`. A `.cmd`/`.bat` target (every npm-installed CLI) cannot be
168
+ * spawned directly — Node refuses since CVE-2024-27980 — so it is launched
169
+ * through `%ComSpec% /d /s /c` with every token escaped by {@link quoteCmdArg}.
170
+ *
171
+ * @param command - Command name or path.
172
+ * @param args - Literal arguments.
173
+ * @param opts - Platform and environment overrides.
174
+ * @returns The spawnable invocation.
175
+ * @throws When a batch-file argument contains a line break (see {@link quoteCmdArg}).
176
+ * @example
177
+ * ```ts
178
+ * const inv = resolveSpawnInvocation('codex', ['--full-auto', promptFile]);
179
+ * spawn(inv.file, inv.args, { windowsVerbatimArguments: inv.windowsVerbatimArguments });
180
+ * ```
181
+ */
182
+ export declare function resolveSpawnInvocation(command: string, args: readonly string[], opts?: ExecPathOptions): SpawnInvocation;
183
+ //# sourceMappingURL=exec-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-path.d.ts","sourceRoot":"","sources":["../src/exec-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/C;AAKD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,MAAM,CAErF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,CAGR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,EAAE,CAEV;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,CAER;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,MAAM,EAAE,CAWlF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,MAAM,GAAG,IAAI,CAgBlF;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,6EAA6E;IAC7E,wBAAwB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,eAAe,CAS5F;AAKD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK/C;AA2BD,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,wEAAwE;IACxE,wBAAwB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,GAAE,eAAoB,GACzB,eAAe,CAYjB"}
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Cross-platform PATH composition and executable lookup.
3
+ *
4
+ * `PATH` is `:`-delimited on POSIX and `;`-delimited on Windows, where the
5
+ * variable is also commonly spelled `Path` and a bare command name resolves
6
+ * through `PATHEXT` (`git` → `git.exe`, `cleo` → `cleo.cmd`). Building PATH as
7
+ * `${dir}:${PATH}` on Windows fuses the new directory with the first real
8
+ * entry, and splitting on `:` cuts every `C:\…` drive-letter entry in half, so
9
+ * each helper here takes the platform explicitly (defaulting to the running
10
+ * one) and joins with that platform's path module. Tests inject `win32` on
11
+ * any host.
12
+ *
13
+ * Lookups are in-process filesystem probes — no `which`/`where` child — so
14
+ * they behave identically on every OS and cost no process spawn.
15
+ *
16
+ * @task T12605
17
+ */
18
+ import { accessSync, constants, statSync } from 'node:fs';
19
+ import { posix, win32 } from 'node:path';
20
+ /** `PATHEXT` used on Windows when the variable is unset. */
21
+ const DEFAULT_PATHEXT = '.COM;.EXE;.BAT;.CMD';
22
+ /**
23
+ * Return the PATH list delimiter for a platform (`;` on win32, `:` elsewhere).
24
+ *
25
+ * @param platform - Target platform. @defaultValue `process.platform`
26
+ * @returns The delimiter character.
27
+ */
28
+ export function pathDelimiterFor(platform = process.platform) {
29
+ return platform === 'win32' ? win32.delimiter : posix.delimiter;
30
+ }
31
+ /**
32
+ * Return the environment key that holds the executable search path.
33
+ *
34
+ * Windows environment names are case-insensitive and the system spelling is
35
+ * `Path`; writing a second `PATH` key beside it leaves the child with two
36
+ * conflicting values. The existing key is reused whatever its case.
37
+ *
38
+ * @param env - Environment to inspect.
39
+ * @param platform - Target platform. @defaultValue `process.platform`
40
+ * @returns `PATH`, or the existing case-variant on win32.
41
+ */
42
+ export function pathEnvKey(env = process.env, platform = process.platform) {
43
+ if (platform !== 'win32')
44
+ return 'PATH';
45
+ return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') ?? 'PATH';
46
+ }
47
+ /**
48
+ * Split a PATH value into its non-empty directory entries.
49
+ *
50
+ * @param value - Raw PATH value (may be undefined).
51
+ * @param platform - Target platform. @defaultValue `process.platform`
52
+ * @returns Directory entries in search order.
53
+ * @example
54
+ * ```ts
55
+ * splitPathEnv('C:\\Windows;C:\\Git\\cmd', 'win32'); // ['C:\\Windows', 'C:\\Git\\cmd']
56
+ * ```
57
+ */
58
+ export function splitPathEnv(value, platform = process.platform) {
59
+ return (value ?? '').split(pathDelimiterFor(platform)).filter(Boolean);
60
+ }
61
+ /**
62
+ * Prepend a directory to a PATH value using the platform's delimiter.
63
+ *
64
+ * @param dir - Directory to search first.
65
+ * @param currentPath - Existing PATH value (may be empty or undefined).
66
+ * @param platform - Target platform. @defaultValue `process.platform`
67
+ * @returns The composed PATH value; `dir` alone when `currentPath` is empty.
68
+ * @example
69
+ * ```ts
70
+ * prependPathEntry('C:\\shim', 'C:\\Windows', 'win32'); // 'C:\\shim;C:\\Windows'
71
+ * ```
72
+ */
73
+ export function prependPathEntry(dir, currentPath, platform = process.platform) {
74
+ return currentPath ? `${dir}${pathDelimiterFor(platform)}${currentPath}` : dir;
75
+ }
76
+ /**
77
+ * List the file names a bare command can resolve to on a platform.
78
+ *
79
+ * On win32 each `PATHEXT` extension is tried, and the bare name only when it
80
+ * already carries one of those extensions. Elsewhere the name is used as-is.
81
+ *
82
+ * @param name - Command name, e.g. `git`.
83
+ * @param opts - Platform and environment overrides.
84
+ * @returns Candidate file names in resolution order.
85
+ */
86
+ export function executableNames(name, opts = {}) {
87
+ const platform = opts.platform ?? process.platform;
88
+ if (platform !== 'win32')
89
+ return [name];
90
+ const env = opts.env ?? process.env;
91
+ const exts = (env[pathExtKey(env)] || DEFAULT_PATHEXT)
92
+ .split(';')
93
+ .map((ext) => ext.trim().toLowerCase())
94
+ .filter(Boolean);
95
+ const lower = name.toLowerCase();
96
+ if (exts.some((ext) => lower.endsWith(ext)))
97
+ return [name];
98
+ return exts.map((ext) => `${name}${ext}`);
99
+ }
100
+ /**
101
+ * Resolve a command to the absolute path the OS would run, or `null`.
102
+ *
103
+ * Honours the platform's PATH delimiter, the win32 `Path` spelling, and
104
+ * `PATHEXT`. A name containing a path separator is checked directly rather
105
+ * than searched for.
106
+ *
107
+ * @param name - Command name or path.
108
+ * @param opts - Platform, environment and executability overrides.
109
+ * @returns Absolute path of the first match, or `null`.
110
+ * @example
111
+ * ```ts
112
+ * findOnPath('git'); // '/usr/bin/git' on Linux, 'C:\\Program Files\\Git\\cmd\\git.exe' on Windows
113
+ * ```
114
+ */
115
+ export function findOnPath(name, opts = {}) {
116
+ const platform = opts.platform ?? process.platform;
117
+ const env = opts.env ?? process.env;
118
+ const pathApi = platform === 'win32' ? win32 : posix;
119
+ const isExecutable = opts.isExecutable ?? ((p) => defaultIsExecutable(p, platform));
120
+ const names = executableNames(name, { platform, env });
121
+ const hasSeparator = platform === 'win32' ? /[\\/]/.test(name) : name.includes('/');
122
+ const dirs = hasSeparator ? [''] : splitPathEnv(env[pathEnvKey(env, platform)], platform);
123
+ for (const dir of dirs) {
124
+ for (const candidateName of names) {
125
+ const candidate = dir ? pathApi.join(dir, candidateName) : candidateName;
126
+ if (isExecutable(candidate))
127
+ return pathApi.resolve(candidate);
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Resolve how to run a shell command line on a platform.
134
+ *
135
+ * `/bin/sh -c` on POSIX; on win32 `%ComSpec%` (cmd.exe) with `/d /s /c` and
136
+ * the line quoted verbatim — the same resolution Node applies for
137
+ * `shell: true`, made explicit so every caller that runs user-authored
138
+ * command strings (worktree hooks, release build commands) shares one
139
+ * resolver and tests can pin the platform.
140
+ *
141
+ * @param command - Command line to run.
142
+ * @param opts - Platform and environment overrides.
143
+ * @returns The executable, its argv and the verbatim-arguments flag.
144
+ * @example
145
+ * ```ts
146
+ * const sh = shellInvocation('pnpm build');
147
+ * execFile(sh.file, sh.args, { windowsVerbatimArguments: sh.windowsVerbatimArguments });
148
+ * ```
149
+ */
150
+ export function shellInvocation(command, opts = {}) {
151
+ const platform = opts.platform ?? process.platform;
152
+ if (platform !== 'win32') {
153
+ return { file: '/bin/sh', args: ['-c', command], windowsVerbatimArguments: false };
154
+ }
155
+ const env = opts.env ?? process.env;
156
+ const comSpecKey = Object.keys(env).find((key) => key.toUpperCase() === 'COMSPEC');
157
+ const file = (comSpecKey && env[comSpecKey]) || 'cmd.exe';
158
+ return { file, args: ['/d', '/s', '/c', `"${command}"`], windowsVerbatimArguments: true };
159
+ }
160
+ /** cmd.exe metacharacters; each is caret-escaped so cmd treats it literally. */
161
+ const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
162
+ /**
163
+ * Quote one argument for a `.cmd`/`.bat` target launched through cmd.exe.
164
+ *
165
+ * Two layers, as cmd parses the line and the batch file's program parses its
166
+ * argv: MSVCRT quoting (backslashes before a quote doubled, the quote
167
+ * escaped, the whole argument wrapped in quotes), then every cmd
168
+ * metacharacter caret-escaped TWICE — cmd re-parses the line when it
169
+ * dispatches a batch file, consuming one caret layer each pass. Because every
170
+ * quote is escaped, cmd never sees a quoted region in which operators such as
171
+ * `&` or `|` would be live. `%VAR%` expands before carets are processed, but
172
+ * `%NAME^%` names a variable (`NAME^`) that is never defined, and an undefined
173
+ * reference is left verbatim on a `cmd /c` line. This is the scheme `cross-spawn` uses against the BatBadBut class
174
+ * (CVE-2024-27980).
175
+ *
176
+ * @param arg - Literal argument.
177
+ * @returns The escaped token.
178
+ * @throws When `arg` holds CR or LF: cmd.exe ends the command at a line
179
+ * break and no escape can carry one into a batch file.
180
+ * @example
181
+ * ```ts
182
+ * quoteCmdArg('a b'); // '^^^"a^^^ b^^^"'
183
+ * ```
184
+ */
185
+ export function quoteCmdArg(arg) {
186
+ if (/[\r\n]/.test(arg))
187
+ throw new Error('E_UNSAFE_BATCH_ARG: a .cmd/.bat argument cannot contain a line break');
188
+ const msvcrt = `"${escapeMsvcrt(arg)}"`;
189
+ return msvcrt.replace(CMD_META, '^$1').replace(CMD_META, '^$1');
190
+ }
191
+ /**
192
+ * MSVCRT-escape an argument's body in ONE linear pass: a run of backslashes
193
+ * is doubled when it precedes a quote (the quote itself is then escaped) or
194
+ * the end of the argument, and kept as-is otherwise. The regex form
195
+ * `/(\\*)"/g` is quadratic on long backslash runs — the ReDoS class of
196
+ * cross-spawn's CVE-2024-21538 — and this input is untrusted prompt text.
197
+ */
198
+ function escapeMsvcrt(arg) {
199
+ let out = '';
200
+ let slashes = 0;
201
+ for (const ch of arg) {
202
+ if (ch === '\\') {
203
+ slashes++;
204
+ continue;
205
+ }
206
+ if (ch === '"') {
207
+ out += `${'\\'.repeat(slashes * 2)}\\"`;
208
+ }
209
+ else {
210
+ out += `${'\\'.repeat(slashes)}${ch}`;
211
+ }
212
+ slashes = 0;
213
+ }
214
+ return out + '\\'.repeat(slashes * 2);
215
+ }
216
+ /**
217
+ * Resolve how to spawn `command args` without a shell on any platform.
218
+ *
219
+ * POSIX: returned unchanged. win32: the command is resolved to the absolute
220
+ * path {@link findOnPath} finds, because Node's own lookup tries only `.exe`
221
+ * and `.com`. A `.cmd`/`.bat` target (every npm-installed CLI) cannot be
222
+ * spawned directly — Node refuses since CVE-2024-27980 — so it is launched
223
+ * through `%ComSpec% /d /s /c` with every token escaped by {@link quoteCmdArg}.
224
+ *
225
+ * @param command - Command name or path.
226
+ * @param args - Literal arguments.
227
+ * @param opts - Platform and environment overrides.
228
+ * @returns The spawnable invocation.
229
+ * @throws When a batch-file argument contains a line break (see {@link quoteCmdArg}).
230
+ * @example
231
+ * ```ts
232
+ * const inv = resolveSpawnInvocation('codex', ['--full-auto', promptFile]);
233
+ * spawn(inv.file, inv.args, { windowsVerbatimArguments: inv.windowsVerbatimArguments });
234
+ * ```
235
+ */
236
+ export function resolveSpawnInvocation(command, args, opts = {}) {
237
+ const platform = opts.platform ?? process.platform;
238
+ if (platform !== 'win32') {
239
+ return { file: command, args: [...args], windowsVerbatimArguments: false };
240
+ }
241
+ const resolved = findOnPath(command, opts) ?? command;
242
+ if (!/\.(cmd|bat)$/i.test(resolved)) {
243
+ return { file: resolved, args: [...args], windowsVerbatimArguments: false };
244
+ }
245
+ const line = [resolved.replace(CMD_META, '^$1'), ...args.map(quoteCmdArg)].join(' ');
246
+ const shell = shellInvocation(line, opts);
247
+ return { file: shell.file, args: shell.args, windowsVerbatimArguments: true };
248
+ }
249
+ /** Environment key for `PATHEXT`, matched case-insensitively. */
250
+ function pathExtKey(env) {
251
+ return Object.keys(env).find((key) => key.toUpperCase() === 'PATHEXT') ?? 'PATHEXT';
252
+ }
253
+ /** A regular file that the current user may execute (existence alone on win32). */
254
+ function defaultIsExecutable(candidate, platform) {
255
+ try {
256
+ if (!statSync(candidate).isFile())
257
+ return false;
258
+ if (platform !== 'win32')
259
+ accessSync(candidate, constants.X_OK);
260
+ return true;
261
+ }
262
+ catch {
263
+ return false;
264
+ }
265
+ }
266
+ //# sourceMappingURL=exec-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-path.js","sourceRoot":"","sources":["../src/exec-path.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -14,7 +14,11 @@
14
14
  * - Worktree primitives: {@link computeProjectHash},
15
15
  * {@link resolveWorktreeRootForHash}, {@link resolveTaskWorktreePath},
16
16
  * {@link getCleoWorktreesRoot}, {@link resolveWorktreeIndexPath}
17
+ * - {@link getCleoStateDir} — CLEO state dir (XDG state on Linux, `<cleoHome>/state` elsewhere)
18
+ * - {@link expandTildePath} — `~` expansion via `os.homedir()`
17
19
  * - {@link isAbsolutePath} — cross-platform abs-path check
20
+ * - Executable search: {@link findOnPath}, {@link prependPathEntry},
21
+ * {@link splitPathEnv} (PATH delimiter + PATHEXT aware, T12605)
18
22
  * - Portable identity: {@link readPortableProjectId} (tracked `.cleo/project-id`, T12325)
19
23
  *
20
24
  * @packageDocumentation
@@ -22,9 +26,10 @@
22
26
  * @task T11008
23
27
  */
24
28
  export { isAbsolutePath } from './abs-path.js';
25
- export { _resetCleoPlatformPathsCache, canonicalizePath, computeCanonicalProjectId, getCanonicalTemplatesTildePath, getCleoHome, getCleoPlatformPaths, getCleoSystemInfo, getCleoTemplatesTildePath, legacyProjectId, type ResolvedProject, resolveCanonicalCleoDir, resolveLegacyCleoDir, resolveProjectByCwd, } from './cleo-paths.js';
29
+ export { _resetCleoPlatformPathsCache, canonicalizePath, computeCanonicalProjectId, computePathFingerprintId, type DeclaredProjectIdentity, getCanonicalTemplatesTildePath, getCleoHome, getCleoPlatformPaths, getCleoStateDir, getCleoSystemInfo, getCleoTemplatesTildePath, legacyAliasClaimants, legacyProjectId, type RecordedProjectPath, type ResolvedProject, readDeclaredProjectIdentity, resolveCanonicalCleoDir, resolveLegacyCleoDir, resolveProjectByCwd, resolveStableDeviceIdPath, } from './cleo-paths.js';
30
+ export { type ExecPathOptions, executableNames, findOnPath, pathDelimiterFor, pathEnvKey, prependPathEntry, quoteCmdArg, resolveSpawnInvocation, type ShellInvocation, type SpawnInvocation, shellInvocation, splitPathEnv, } from './exec-path.js';
26
31
  export { type EnforceOptions, enforceNodeVersion, evaluateNodeVersion, FALLBACK_MIN_NODE, getRequiredNodeVersion, type NodeManager, type NodeVersionVerdict, parseSemver, type Semver, type UpgradeHint, } from './node-version-gate.js';
27
- export { createPlatformPathsResolver, type PlatformPaths, type PlatformPathsResolver, type SystemInfo, } from './platform-paths.js';
32
+ export { createPlatformPathsResolver, expandTildePath, type PlatformPaths, type PlatformPathsResolver, type SystemInfo, } from './platform-paths.js';
28
33
  export { formatPortableProjectId, isValidPortableProjectId, PORTABLE_PROJECT_ID_FILE, type PortableProjectIdRead, parsePortableProjectId, portableProjectIdPath, readPortableProjectId, } from './portable-project-id.js';
29
- export { computeProjectHash, getCleoWorktreesRoot, resolveTaskWorktreePath, resolveWorktreeIndexPath, resolveWorktreeRootForHash, } from './worktree-paths.js';
34
+ export { computeProjectHash, getCleoWorktreesRoot, resolveTaskWorktreePath, resolveWorktreeIndexPath, resolveWorktreeRootForHash, resolveWorktreeTaskLockPath, } from './worktree-paths.js';
30
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EACL,4BAA4B,EAC5B,gBAAgB,EAChB,yBAAyB,EACzB,8BAA8B,EAC9B,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,eAAe,EACf,KAAK,eAAe,EACpB,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,cAAc,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,WAAW,EACX,KAAK,MAAM,EACX,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,2BAA2B,EAC3B,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACxB,KAAK,qBAAqB,EAC1B,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EACL,4BAA4B,EAC5B,gBAAgB,EAChB,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,uBAAuB,EAC5B,8BAA8B,EAC9B,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,2BAA2B,EAC3B,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,eAAe,EACpB,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,eAAe,EACf,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,cAAc,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,WAAW,EACX,KAAK,MAAM,EACX,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACxB,KAAK,qBAAqB,EAC1B,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -14,7 +14,11 @@
14
14
  * - Worktree primitives: {@link computeProjectHash},
15
15
  * {@link resolveWorktreeRootForHash}, {@link resolveTaskWorktreePath},
16
16
  * {@link getCleoWorktreesRoot}, {@link resolveWorktreeIndexPath}
17
+ * - {@link getCleoStateDir} — CLEO state dir (XDG state on Linux, `<cleoHome>/state` elsewhere)
18
+ * - {@link expandTildePath} — `~` expansion via `os.homedir()`
17
19
  * - {@link isAbsolutePath} — cross-platform abs-path check
20
+ * - Executable search: {@link findOnPath}, {@link prependPathEntry},
21
+ * {@link splitPathEnv} (PATH delimiter + PATHEXT aware, T12605)
18
22
  * - Portable identity: {@link readPortableProjectId} (tracked `.cleo/project-id`, T12325)
19
23
  *
20
24
  * @packageDocumentation
@@ -22,9 +26,10 @@
22
26
  * @task T11008
23
27
  */
24
28
  export { isAbsolutePath } from './abs-path.js';
25
- export { _resetCleoPlatformPathsCache, canonicalizePath, computeCanonicalProjectId, getCanonicalTemplatesTildePath, getCleoHome, getCleoPlatformPaths, getCleoSystemInfo, getCleoTemplatesTildePath, legacyProjectId, resolveCanonicalCleoDir, resolveLegacyCleoDir, resolveProjectByCwd, } from './cleo-paths.js';
29
+ export { _resetCleoPlatformPathsCache, canonicalizePath, computeCanonicalProjectId, computePathFingerprintId, getCanonicalTemplatesTildePath, getCleoHome, getCleoPlatformPaths, getCleoStateDir, getCleoSystemInfo, getCleoTemplatesTildePath, legacyAliasClaimants, legacyProjectId, readDeclaredProjectIdentity, resolveCanonicalCleoDir, resolveLegacyCleoDir, resolveProjectByCwd, resolveStableDeviceIdPath, } from './cleo-paths.js';
30
+ export { executableNames, findOnPath, pathDelimiterFor, pathEnvKey, prependPathEntry, quoteCmdArg, resolveSpawnInvocation, shellInvocation, splitPathEnv, } from './exec-path.js';
26
31
  export { enforceNodeVersion, evaluateNodeVersion, FALLBACK_MIN_NODE, getRequiredNodeVersion, parseSemver, } from './node-version-gate.js';
27
- export { createPlatformPathsResolver, } from './platform-paths.js';
32
+ export { createPlatformPathsResolver, expandTildePath, } from './platform-paths.js';
28
33
  export { formatPortableProjectId, isValidPortableProjectId, PORTABLE_PROJECT_ID_FILE, parsePortableProjectId, portableProjectIdPath, readPortableProjectId, } from './portable-project-id.js';
29
- export { computeProjectHash, getCleoWorktreesRoot, resolveTaskWorktreePath, resolveWorktreeIndexPath, resolveWorktreeRootForHash, } from './worktree-paths.js';
34
+ export { computeProjectHash, getCleoWorktreesRoot, resolveTaskWorktreePath, resolveWorktreeIndexPath, resolveWorktreeRootForHash, resolveWorktreeTaskLockPath, } from './worktree-paths.js';
30
35
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EACL,4BAA4B,EAC5B,gBAAgB,EAChB,yBAAyB,EACzB,8BAA8B,EAC9B,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,eAAe,EAEf,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EAGtB,WAAW,GAGZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,2BAA2B,GAI5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EAExB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EACL,4BAA4B,EAC5B,gBAAgB,EAChB,yBAAyB,EACzB,wBAAwB,EAExB,8BAA8B,EAC9B,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,EAGf,2BAA2B,EAC3B,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,sBAAsB,EAGtB,eAAe,EACf,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EAGtB,WAAW,GAGZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,2BAA2B,EAC3B,eAAe,GAIhB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EAExB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC"}
@@ -89,6 +89,27 @@ export interface PlatformPathsResolver {
89
89
  */
90
90
  resetCache(): void;
91
91
  }
92
+ /**
93
+ * Expand a leading `~` in a user-supplied path to the user's home directory.
94
+ *
95
+ * Uses `os.homedir()`, which reads `USERPROFILE` on Windows — unlike
96
+ * `process.env.HOME`, which is unset there and turns `~/x` into the relative
97
+ * path `x`. Accepts `~`, `~/x`, and on Windows `~\x`. Any other input,
98
+ * including `~user/x`, is returned unchanged.
99
+ *
100
+ * @param path - Path that may start with `~`.
101
+ * @param home - Home directory to expand into. Defaults to `os.homedir()`.
102
+ * @returns The expanded path, or `path` unchanged when it has no leading `~`.
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * expandTildePath('~/.pi/x.cant'); // "/home/me/.pi/x.cant" on Linux
107
+ * ```
108
+ *
109
+ * @public
110
+ * @task T12608
111
+ */
112
+ export declare function expandTildePath(path: string, home?: string): string;
92
113
  /**
93
114
  * Create a platform-paths resolver bound to a specific app name and home env var.
94
115
  *
@@ -1 +1 @@
1
- {"version":3,"file":"platform-paths.d.ts","sourceRoot":"","sources":["../src/platform-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,KAAK,EAAE,aAAa,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,gBAAgB,IAAI,aAAa,CAAC;IAElC;;;;;OAKG;IACH,aAAa,IAAI,UAAU,CAAC;IAE5B;;;;;OAKG;IACH,UAAU,IAAI,IAAI,CAAC;CACpB;AAqBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,qBAAqB,CAyCvB"}
1
+ {"version":3,"file":"platform-paths.d.ts","sourceRoot":"","sources":["../src/platform-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,KAAK,EAAE,aAAa,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,gBAAgB,IAAI,aAAa,CAAC;IAElC;;;;;OAKG;IACH,aAAa,IAAI,UAAU,CAAC;IAE5B;;;;;OAKG;IACH,UAAU,IAAI,IAAI,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAK9E;AAqBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,qBAAqB,CAyCvB"}
@@ -18,6 +18,32 @@
18
18
  import { arch, homedir, hostname, platform, release } from 'node:os';
19
19
  import { isAbsolute, join } from 'node:path';
20
20
  import envPaths from 'env-paths';
21
+ /**
22
+ * Expand a leading `~` in a user-supplied path to the user's home directory.
23
+ *
24
+ * Uses `os.homedir()`, which reads `USERPROFILE` on Windows — unlike
25
+ * `process.env.HOME`, which is unset there and turns `~/x` into the relative
26
+ * path `x`. Accepts `~`, `~/x`, and on Windows `~\x`. Any other input,
27
+ * including `~user/x`, is returned unchanged.
28
+ *
29
+ * @param path - Path that may start with `~`.
30
+ * @param home - Home directory to expand into. Defaults to `os.homedir()`.
31
+ * @returns The expanded path, or `path` unchanged when it has no leading `~`.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * expandTildePath('~/.pi/x.cant'); // "/home/me/.pi/x.cant" on Linux
36
+ * ```
37
+ *
38
+ * @public
39
+ * @task T12608
40
+ */
41
+ export function expandTildePath(path, home = homedir()) {
42
+ if (path === '~')
43
+ return home;
44
+ const separatorAfterTilde = path.startsWith('~/') || (process.platform === 'win32' && path.startsWith('~\\'));
45
+ return separatorAfterTilde ? join(home, path.slice(2)) : path;
46
+ }
21
47
  /**
22
48
  * Normalize a home-override env var value to an absolute path.
23
49
  *
@@ -33,10 +59,9 @@ function resolveHomeOverride(value) {
33
59
  const trimmed = value.trim();
34
60
  if (trimmed.length === 0)
35
61
  return undefined;
36
- if (trimmed === '~')
37
- return homedir();
38
- if (trimmed.startsWith('~/'))
39
- return join(homedir(), trimmed.slice(2));
62
+ const expanded = expandTildePath(trimmed);
63
+ if (expanded !== trimmed)
64
+ return expanded;
40
65
  if (isAbsolute(trimmed))
41
66
  return trimmed;
42
67
  return join(homedir(), trimmed);
@@ -1 +1 @@
1
- {"version":3,"file":"platform-paths.js","sourceRoot":"","sources":["../src/platform-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,QAAQ,MAAM,WAAW,CAAC;AAiFjC;;;;;;;;GAQG;AACH,SAAS,mBAAmB,CAAC,KAAyB;IACpD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,OAAO,EAAE,CAAC;IACtC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,IAAI,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACxC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,2BAA2B,CACzC,OAAe,EACf,UAAkB,EAClB,YAAqB;IAErB,IAAI,aAAa,GAAsB,IAAI,CAAC;IAE5C,SAAS,iBAAiB;QACxB,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;QAClE,gEAAgE;QAChE,yEAAyE;QACzE,yEAAyE;QACzE,yEAAyE;QACzE,0BAA0B;QAC1B,MAAM,cAAc,GAAG,YAAY;YACjC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAChD,CAAC,CAAC,SAAS,CAAC;QACd,OAAO;YACL,IAAI,EAAE,YAAY,IAAI,EAAE,CAAC,IAAI;YAC7B,MAAM,EAAE,cAAc,IAAI,EAAE,CAAC,MAAM;YACnC,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,GAAG,EAAE,EAAE,CAAC,GAAG;YACX,IAAI,EAAE,EAAE,CAAC,IAAI;SACd,CAAC;IACJ,CAAC;IAED,OAAO;QACL,gBAAgB,EAAE,iBAAiB;QACnC,aAAa;YACX,IAAI,aAAa;gBAAE,OAAO,aAAa,CAAC;YACxC,aAAa,GAAG;gBACd,QAAQ,EAAE,QAAQ,EAAE;gBACpB,IAAI,EAAE,IAAI,EAAE;gBACZ,OAAO,EAAE,OAAO,EAAE;gBAClB,QAAQ,EAAE,QAAQ,EAAE;gBACpB,WAAW,EAAE,OAAO,CAAC,OAAO;gBAC5B,KAAK,EAAE,iBAAiB,EAAE;aAC3B,CAAC;YACF,OAAO,aAAa,CAAC;QACvB,CAAC;QACD,UAAU;YACR,aAAa,GAAG,IAAI,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"platform-paths.js","sourceRoot":"","sources":["../src/platform-paths.ts"],"names":[],"mappings":"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"}