pi-rtk-optimizer 0.7.0 → 0.7.1
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 +16 -1
- package/README.md +8 -5
- package/package.json +11 -8
- package/src/additional-coverage-test.ts +116 -57
- package/src/command-rewriter-test.ts +187 -118
- package/src/command-rewriter.ts +5 -2
- package/src/config-modal-test.ts +95 -29
- package/src/config-modal.ts +13 -3
- package/src/index-test.ts +196 -1
- package/src/index.ts +48 -4
- package/src/output-compactor.ts +22 -15
- package/src/rewrite-pipeline-safety.ts +203 -178
- package/src/rtk-command-environment.ts +73 -69
- package/src/rtk-executable-resolver.ts +97 -0
- package/src/rtk-rewrite-provider.ts +39 -3
- package/src/shell-env-prefix.ts +5 -1
- package/src/test-helpers.ts +23 -10
- package/src/tool-execution-sanitizer.ts +80 -69
- package/src/types.ts +4 -0
- package/src/windows-command-helpers.ts +92 -16
- package/src/zellij-modal.ts +1 -1
|
@@ -1,178 +1,203 @@
|
|
|
1
|
-
import { splitLeadingEnvAssignments } from "./shell-env-prefix.js";
|
|
2
|
-
|
|
3
|
-
interface ParsedPipeline {
|
|
4
|
-
segments: string[];
|
|
5
|
-
separators: string[];
|
|
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
|
-
separators.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (character === "
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
`${
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
1
|
+
import { splitLeadingEnvAssignments } from "./shell-env-prefix.js";
|
|
2
|
+
|
|
3
|
+
interface ParsedPipeline {
|
|
4
|
+
segments: string[];
|
|
5
|
+
separators: string[];
|
|
6
|
+
suffix: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ProducerRewritePlan {
|
|
10
|
+
command: string;
|
|
11
|
+
captureStderr: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ShellSafetyTarget {
|
|
15
|
+
environmentPrelude: string;
|
|
16
|
+
command: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SINGLE_QUOTED_SHELL_VALUE_PATTERN = "'(?:'\\\\''|[^'])*'";
|
|
20
|
+
const SHELL_ENV_VALUE_PATTERN = `(?:"(?:\\\\.|[^"])*"|${SINGLE_QUOTED_SHELL_VALUE_PATTERN}|[^\\s;]+)`;
|
|
21
|
+
const LEADING_RTK_DB_PATH_EXPORT_PRELUDE_PATTERN = new RegExp(
|
|
22
|
+
`^(\\s*export\\s+RTK_DB_PATH=${SHELL_ENV_VALUE_PATTERN}\\s*;\\s*)([\\s\\S]*)$`,
|
|
23
|
+
"u",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
function splitLeadingRtkDbPathExportPrelude(command: string): ShellSafetyTarget {
|
|
27
|
+
const match = command.match(LEADING_RTK_DB_PATH_EXPORT_PRELUDE_PATTERN);
|
|
28
|
+
if (!match) {
|
|
29
|
+
return { environmentPrelude: "", command };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
environmentPrelude: match[1] ?? "",
|
|
34
|
+
command: match[2] ?? "",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isTopLevelQuoteCharacter(character: string): character is '"' | "'" | "`" {
|
|
39
|
+
return character === '"' || character === "'" || character === "`";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseSimpleTopLevelPipeline(command: string): ParsedPipeline | null {
|
|
43
|
+
const segments: string[] = [];
|
|
44
|
+
const separators: string[] = [];
|
|
45
|
+
let quote: '"' | "'" | "`" | null = null;
|
|
46
|
+
let escaped = false;
|
|
47
|
+
let segmentStart = 0;
|
|
48
|
+
let suffix = "";
|
|
49
|
+
|
|
50
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
51
|
+
const character = command[index] ?? "";
|
|
52
|
+
const nextCharacter = command[index + 1] ?? "";
|
|
53
|
+
const previousCharacter = index > 0 ? (command[index - 1] ?? "") : "";
|
|
54
|
+
|
|
55
|
+
if (escaped) {
|
|
56
|
+
escaped = false;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (quote !== null) {
|
|
61
|
+
if (character === "\\" && quote !== "'") {
|
|
62
|
+
escaped = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (character === quote) {
|
|
66
|
+
quote = null;
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (character === "\\") {
|
|
72
|
+
escaped = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isTopLevelQuoteCharacter(character)) {
|
|
77
|
+
quote = character;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (character === "|" && nextCharacter === "|") {
|
|
82
|
+
if (separators.length === 0) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
segments.push(command.slice(segmentStart, index));
|
|
86
|
+
suffix = command.slice(index);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (character === "|" && previousCharacter !== ">") {
|
|
91
|
+
const separatorLength = nextCharacter === "&" ? 2 : 1;
|
|
92
|
+
segments.push(command.slice(segmentStart, index));
|
|
93
|
+
separators.push(command.slice(index, index + separatorLength));
|
|
94
|
+
segmentStart = index + separatorLength;
|
|
95
|
+
if (separatorLength === 2) {
|
|
96
|
+
index += 1;
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (character === "&" && nextCharacter === "&") {
|
|
102
|
+
if (separators.length === 0) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
segments.push(command.slice(segmentStart, index));
|
|
106
|
+
suffix = command.slice(index);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (character === "&" && nextCharacter !== ">" && previousCharacter !== ">" && previousCharacter !== "<") {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (character === ";") {
|
|
115
|
+
if (separators.length === 0) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
segments.push(command.slice(segmentStart, index));
|
|
119
|
+
suffix = command.slice(index);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (separators.length === 0) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!suffix) {
|
|
129
|
+
segments.push(command.slice(segmentStart));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { segments, separators, suffix };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function extractProducerRewritePlan(segment: string, firstSeparator: string): ProducerRewritePlan | null {
|
|
136
|
+
const trimmed = segment.trim();
|
|
137
|
+
const { envPrefix, command: commandWithOptionalRedirect } = splitLeadingEnvAssignments(trimmed);
|
|
138
|
+
if (!/^rtk\s+/i.test(commandWithOptionalRedirect)) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const stderrMergeMatch = commandWithOptionalRedirect.match(/^(.*?)(?:\s+)?2>\s*&1\s*$/u);
|
|
143
|
+
if (stderrMergeMatch) {
|
|
144
|
+
const command = stderrMergeMatch[1]?.trimEnd() ?? "";
|
|
145
|
+
return command ? { command: `${envPrefix}${command}`.trim(), captureStderr: true } : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
command: `${envPrefix}${commandWithOptionalRedirect}`.trim(),
|
|
150
|
+
captureStderr: firstSeparator === "|&",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function buildBufferedPipelineCommand(
|
|
155
|
+
producer: ProducerRewritePlan,
|
|
156
|
+
remainder: string,
|
|
157
|
+
): string {
|
|
158
|
+
const tempFileVariable = "__pi_rtk_pipe_tmp";
|
|
159
|
+
const statusVariable = "__pi_rtk_pipe_status";
|
|
160
|
+
const producerRedirect = producer.captureStderr ? `> "$${tempFileVariable}" 2>&1` : `> "$${tempFileVariable}"`;
|
|
161
|
+
const cleanupTrap = `rm -f "$${tempFileVariable}"`;
|
|
162
|
+
|
|
163
|
+
return [
|
|
164
|
+
"{",
|
|
165
|
+
`${tempFileVariable}="$(mktemp)" || exit $?;`,
|
|
166
|
+
`${statusVariable}=0;`,
|
|
167
|
+
`trap '${cleanupTrap}' EXIT HUP INT TERM;`,
|
|
168
|
+
`${producer.command} ${producerRedirect};`,
|
|
169
|
+
`${statusVariable}=$?;`,
|
|
170
|
+
`if [ $${statusVariable} -eq 0 ]; then (${remainder}) < "$${tempFileVariable}"; ${statusVariable}=$?; fi;`,
|
|
171
|
+
`exit $${statusVariable};`,
|
|
172
|
+
"}",
|
|
173
|
+
].join(" ");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function applyRewrittenCommandShellSafetyFixups(command: string, platform: string = process.platform): string {
|
|
177
|
+
if (platform !== "win32") {
|
|
178
|
+
return command;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const target = splitLeadingRtkDbPathExportPrelude(command);
|
|
182
|
+
const parsedPipeline = parseSimpleTopLevelPipeline(target.command);
|
|
183
|
+
if (!parsedPipeline) {
|
|
184
|
+
return command;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const producer = extractProducerRewritePlan(parsedPipeline.segments[0] ?? "", parsedPipeline.separators[0] ?? "");
|
|
188
|
+
if (!producer) {
|
|
189
|
+
return command;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const remainder = parsedPipeline.segments
|
|
193
|
+
.slice(1)
|
|
194
|
+
.map((segment, index) => `${index === 0 ? "" : (parsedPipeline.separators[index] ?? "")}${segment}`)
|
|
195
|
+
.join("")
|
|
196
|
+
.trim();
|
|
197
|
+
if (!remainder) {
|
|
198
|
+
return command;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const suffix = parsedPipeline.suffix ? ` ${parsedPipeline.suffix.trimStart()}` : "";
|
|
202
|
+
return `${target.environmentPrelude}${buildBufferedPipelineCommand(producer, remainder)}${suffix}`;
|
|
203
|
+
}
|
|
@@ -1,69 +1,73 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
|
|
3
|
-
import { splitLeadingEnvAssignments } from "./shell-env-prefix.js";
|
|
4
|
-
|
|
5
|
-
const RTK_DB_PATH_ENV_NAME = "RTK_DB_PATH";
|
|
6
|
-
const
|
|
7
|
-
const
|
|
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
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
return command;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { splitLeadingEnvAssignments } from "./shell-env-prefix.js";
|
|
4
|
+
|
|
5
|
+
const RTK_DB_PATH_ENV_NAME = "RTK_DB_PATH";
|
|
6
|
+
const SINGLE_QUOTED_SHELL_VALUE_PATTERN = "'(?:'\\\\''|[^'])*'";
|
|
7
|
+
const SHELL_ENV_VALUE_PATTERN = `(?:"[^"]*"|${SINGLE_QUOTED_SHELL_VALUE_PATTERN}|[^\\s;]+)`;
|
|
8
|
+
const RTK_DB_PATH_ASSIGNMENT_PATTERN = new RegExp(
|
|
9
|
+
`(?:^|\\s)RTK_DB_PATH=${SHELL_ENV_VALUE_PATTERN}(?=\\s|$)`,
|
|
10
|
+
);
|
|
11
|
+
const RTK_DB_PATH_EXPORT_PATTERN = new RegExp(`^export\\s+RTK_DB_PATH=${SHELL_ENV_VALUE_PATTERN}(?=\\s*(?:;|$))`);
|
|
12
|
+
|
|
13
|
+
function resolveTemporaryDirectory(): string {
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
const windowsTempDir = process.env.TEMP ?? process.env.TMP;
|
|
16
|
+
if (windowsTempDir && windowsTempDir.trim()) {
|
|
17
|
+
return windowsTempDir;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
21
|
+
if (localAppData && localAppData.trim()) {
|
|
22
|
+
return join(localAppData, "Temp");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const userProfile = process.env.USERPROFILE;
|
|
26
|
+
if (userProfile && userProfile.trim()) {
|
|
27
|
+
return join(userProfile, "AppData", "Local", "Temp");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const systemRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
31
|
+
if (systemRoot && systemRoot.trim()) {
|
|
32
|
+
return join(systemRoot, "Temp");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return "C:/Windows/Temp";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const posixTempDir = process.env.TMPDIR ?? process.env.TMP;
|
|
39
|
+
if (posixTempDir && posixTempDir.trim()) {
|
|
40
|
+
return posixTempDir;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return "/tmp";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getTemporaryRtkHistoryDbPath(): string {
|
|
47
|
+
return join(resolveTemporaryDirectory(), "pi-rtk-optimizer", "history.db");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function quoteForShellEnv(value: string): string {
|
|
51
|
+
const normalizedValue = process.platform === "win32" ? value.replace(/\\/g, "/") : value;
|
|
52
|
+
return `'${normalizedValue.replace(/'/g, `'\\''`)}'`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function hasLeadingRtkDbPathAssignment(command: string): boolean {
|
|
56
|
+
const trimmed = command.trimStart();
|
|
57
|
+
return (
|
|
58
|
+
RTK_DB_PATH_ASSIGNMENT_PATTERN.test(splitLeadingEnvAssignments(trimmed).envPrefix) ||
|
|
59
|
+
RTK_DB_PATH_EXPORT_PATTERN.test(trimmed)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function applyRtkCommandEnvironment(command: string): string {
|
|
64
|
+
if (!command.trim()) {
|
|
65
|
+
return command;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (hasLeadingRtkDbPathAssignment(command)) {
|
|
69
|
+
return command;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return `export ${RTK_DB_PATH_ENV_NAME}=${quoteForShellEnv(getTemporaryRtkHistoryDbPath())}; ${command}`;
|
|
73
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export type RtkExecutableResolverName = "where" | "which";
|
|
4
|
+
|
|
5
|
+
export interface RtkExecutableResolution {
|
|
6
|
+
command: string;
|
|
7
|
+
resolvedPath?: string;
|
|
8
|
+
resolver: RtkExecutableResolverName;
|
|
9
|
+
warning?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ResolverCommand {
|
|
13
|
+
command: RtkExecutableResolverName;
|
|
14
|
+
args: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ResolveRtkExecutableOptions {
|
|
18
|
+
platform?: typeof process.platform;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function trimResolutionDetail(value: string | undefined): string {
|
|
23
|
+
return (value ?? "").replace(/\s+/g, " ").trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function stripWrappingQuotes(value: string): string {
|
|
27
|
+
if (value.length < 2) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const first = value[0];
|
|
32
|
+
const last = value[value.length - 1];
|
|
33
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
34
|
+
return value.slice(1, -1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseRtkExecutablePath(stdout: string): string | undefined {
|
|
41
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
42
|
+
const candidate = stripWrappingQuotes(line.trim());
|
|
43
|
+
if (candidate) {
|
|
44
|
+
return candidate;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getResolverCommand(platform: typeof process.platform): ResolverCommand {
|
|
52
|
+
if (platform === "win32") {
|
|
53
|
+
return { command: "where", args: ["rtk"] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { command: "which", args: ["rtk"] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fallbackResolution(resolver: RtkExecutableResolverName, warning: string): RtkExecutableResolution {
|
|
60
|
+
return {
|
|
61
|
+
command: "rtk",
|
|
62
|
+
resolver,
|
|
63
|
+
warning,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function resolveRtkExecutable(
|
|
68
|
+
pi: ExtensionAPI,
|
|
69
|
+
options: ResolveRtkExecutableOptions = {},
|
|
70
|
+
): Promise<RtkExecutableResolution> {
|
|
71
|
+
const resolver = getResolverCommand(options.platform ?? process.platform);
|
|
72
|
+
const timeout = options.timeoutMs ?? 1000;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const result = await pi.exec(resolver.command, resolver.args, { timeout });
|
|
76
|
+
const resolvedPath = parseRtkExecutablePath(result.stdout ?? "");
|
|
77
|
+
if (result.code === 0 && resolvedPath) {
|
|
78
|
+
return {
|
|
79
|
+
command: resolvedPath,
|
|
80
|
+
resolvedPath,
|
|
81
|
+
resolver: resolver.command,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const detail = trimResolutionDetail(result.stderr || result.stdout || `exit ${result.code}`);
|
|
86
|
+
return fallbackResolution(
|
|
87
|
+
resolver.command,
|
|
88
|
+
`rtk executable path resolution via ${resolver.command} failed${detail ? `: ${detail}` : ""}`,
|
|
89
|
+
);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
return fallbackResolution(
|
|
93
|
+
resolver.command,
|
|
94
|
+
`rtk executable path resolution via ${resolver.command} failed: ${trimResolutionDetail(message)}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|