-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathutil.ts
More file actions
105 lines (96 loc) · 3.72 KB
/
Copy pathutil.ts
File metadata and controls
105 lines (96 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import os from "node:os";
// Regex patterns to find the SSH port from Remote SSH extension logs.
// `ms-vscode-remote.remote-ssh`: `-> socksPort <port> ->` or `between local port <port>`
// `codeium.windsurf-remote-openssh`, `jeanp413.open-remote-ssh`, `google.antigravity-remote-openssh`: `=> <port>(socks) =>`
// `anysphere.remote-ssh`: `Socks port: <port>`
export const RemoteSSHLogPortRegex =
/(?:-> socksPort (\d+) ->|between local port (\d+)|=> (\d+)\(socks\) =>|Socks port: (\d+))/g;
/**
* Given the contents of a Remote - SSH log file, find the most recent port
* number used by the SSH process. This is typically the socks port, but the
* local port works too.
*
* Returns null if no port is found.
*/
export function findPort(text: string): number | null {
const allMatches = [...text.matchAll(RemoteSSHLogPortRegex)];
if (allMatches.length === 0) {
return null;
}
// Get the last match, which is the most recent port.
const lastMatch = allMatches[allMatches.length - 1];
// Each capture group corresponds to a different Remote SSH extension log format:
// [0] full match, [1] and [2] ms-vscode-remote.remote-ssh,
// [3] devin/windsurf/open-remote-ssh/antigravity, [4] anysphere.remote-ssh
const portStr = lastMatch[1] || lastMatch[2] || lastMatch[3] || lastMatch[4];
if (!portStr) {
return null;
}
return Number.parseInt(portStr);
}
/**
* Substitute `${env:VAR}` with `process.env.VAR` (unset → empty string),
* `${userHome}` (anywhere) with `os.homedir()`, and a leading `~` with
* `os.homedir()`. Env substitution runs first so env values can themselves
* contain `~` or `${userHome}`.
*/
export function expandPath(input: string): string {
const expanded = input.replace(
/\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g,
(_, name: string) => process.env[name] ?? "",
);
const userHome = os.homedir();
const tildeExpanded = expanded.startsWith("~")
? userHome + expanded.substring("~".length)
: expanded;
return tildeExpanded.replaceAll("${userHome}", userHome);
}
/** `toLowerCase` typed for indexing `Lowercase`-keyed records without a cast. */
export function lowercase<T extends string>(value: T): Lowercase<T> {
return value.toLowerCase() as Lowercase<T>;
}
/**
* Return the number of times a substring appears in a string.
*/
export function countSubstring(needle: string, haystack: string): number {
if (needle.length < 1 || haystack.length < 1) {
return 0;
}
let count = 0;
let pos = haystack.indexOf(needle);
while (pos !== -1) {
count++;
pos = haystack.indexOf(needle, pos + needle.length);
}
return count;
}
/**
* Wraps `arg` in `"..."` unless every character is in the shell-safe
* whitelist (matching Python `shlex.quote`'s set: alphanumerics plus
* `@%+,=:./-`). Anything else (whitespace, `"`, `&|;()<>*?[~#!^\` `$`)
* forces quoting so the output is a single token in POSIX `sh`, cmd.exe,
* and PowerShell.
*
* Not a universal shell-escape: `$VAR` / `$(...)` / `%VAR%` still expand
* inside `"..."`. For untrusted values use {@link escapeShellArg}.
*
* @see https://docs.python.org/3/library/shlex.html#shlex.quote
* @see https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
*/
export function escapeCommandArg(arg: string): string {
if (arg !== "" && /^[\w@%+,=:./-]+$/.test(arg)) {
return arg;
}
return `"${arg.replaceAll('"', String.raw`\"`)}"`;
}
/**
* Cross-platform shell quoting that blocks variable expansion. Use for
* values from outside the user's local settings (e.g. server-controlled).
*/
export function escapeShellArg(arg: string): string {
if (os.platform() === "win32") {
const escaped = arg.replace(/"/g, '""').replace(/%/g, "%%");
return `"${escaped}"`;
}
return `'${arg.replace(/'/g, "'\\''")}'`;
}