pi-rtk-optimizer 0.6.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 +24 -0
- package/README.md +19 -12
- package/config/config.example.json +6 -3
- package/package.json +67 -64
- package/src/additional-coverage-test.ts +151 -50
- 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 +29 -3
- package/src/config-store.ts +32 -5
- package/src/index-test.ts +227 -3
- package/src/index.ts +50 -5
- package/src/output-compactor-test.ts +45 -2
- package/src/output-compactor.ts +26 -15
- package/src/rewrite-pipeline-safety.ts +203 -157
- package/src/rtk-command-environment.ts +13 -4
- 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 +13 -3
- package/src/windows-command-helpers.ts +92 -16
- package/src/zellij-modal.ts +1 -1
package/src/output-compactor.ts
CHANGED
|
@@ -140,6 +140,10 @@ function shouldPreserveExactReadOutput(
|
|
|
140
140
|
input: Record<string, unknown>,
|
|
141
141
|
config: RtkIntegrationConfig,
|
|
142
142
|
): boolean {
|
|
143
|
+
if (!config.outputCompaction.readCompaction.enabled) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
143
147
|
if (hasExplicitReadRange(input)) {
|
|
144
148
|
return true;
|
|
145
149
|
}
|
|
@@ -186,6 +190,10 @@ function hasLossyCompaction(techniques: string[]): boolean {
|
|
|
186
190
|
);
|
|
187
191
|
}
|
|
188
192
|
|
|
193
|
+
function normalizeTechniqueResult(result: string | null, currentText: string): string {
|
|
194
|
+
return result === null ? currentText : result;
|
|
195
|
+
}
|
|
196
|
+
|
|
189
197
|
function compactBashText(
|
|
190
198
|
text: string,
|
|
191
199
|
command: string | undefined,
|
|
@@ -203,45 +211,45 @@ function compactBashText(
|
|
|
203
211
|
}
|
|
204
212
|
}
|
|
205
213
|
|
|
206
|
-
const withoutRtkHookWarnings = stripRtkHookWarnings(nextText, command);
|
|
207
|
-
if (withoutRtkHookWarnings !==
|
|
214
|
+
const withoutRtkHookWarnings = normalizeTechniqueResult(stripRtkHookWarnings(nextText, command), nextText);
|
|
215
|
+
if (withoutRtkHookWarnings !== nextText) {
|
|
208
216
|
nextText = withoutRtkHookWarnings;
|
|
209
217
|
techniques.push("rtk-hook-warning");
|
|
210
218
|
}
|
|
211
219
|
|
|
212
|
-
const withoutRtkEmoji = sanitizeRtkEmojiOutput(nextText, command);
|
|
213
|
-
if (withoutRtkEmoji !==
|
|
220
|
+
const withoutRtkEmoji = normalizeTechniqueResult(sanitizeRtkEmojiOutput(nextText, command), nextText);
|
|
221
|
+
if (withoutRtkEmoji !== nextText) {
|
|
214
222
|
nextText = withoutRtkEmoji;
|
|
215
223
|
techniques.push("rtk-emoji");
|
|
216
224
|
}
|
|
217
225
|
|
|
218
226
|
if (compaction.filterBuildOutput) {
|
|
219
|
-
const compacted = filterBuildOutput(nextText, command);
|
|
220
|
-
if (compacted !==
|
|
227
|
+
const compacted = normalizeTechniqueResult(filterBuildOutput(nextText, command), nextText);
|
|
228
|
+
if (compacted !== nextText) {
|
|
221
229
|
nextText = compacted;
|
|
222
230
|
techniques.push("build");
|
|
223
231
|
}
|
|
224
232
|
}
|
|
225
233
|
|
|
226
234
|
if (compaction.aggregateTestOutput) {
|
|
227
|
-
const compacted = aggregateTestOutput(nextText, command);
|
|
228
|
-
if (compacted !==
|
|
235
|
+
const compacted = normalizeTechniqueResult(aggregateTestOutput(nextText, command), nextText);
|
|
236
|
+
if (compacted !== nextText) {
|
|
229
237
|
nextText = compacted;
|
|
230
238
|
techniques.push("test");
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
241
|
|
|
234
242
|
if (compaction.compactGitOutput) {
|
|
235
|
-
const compacted = compactGitOutput(nextText, command);
|
|
236
|
-
if (compacted !==
|
|
243
|
+
const compacted = normalizeTechniqueResult(compactGitOutput(nextText, command), nextText);
|
|
244
|
+
if (compacted !== nextText) {
|
|
237
245
|
nextText = compacted;
|
|
238
246
|
techniques.push("git");
|
|
239
247
|
}
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
if (compaction.aggregateLinterOutput) {
|
|
243
|
-
const compacted = aggregateLinterOutput(nextText, command);
|
|
244
|
-
if (compacted !==
|
|
251
|
+
const compacted = normalizeTechniqueResult(aggregateLinterOutput(nextText, command), nextText);
|
|
252
|
+
if (compacted !== nextText) {
|
|
245
253
|
nextText = compacted;
|
|
246
254
|
techniques.push("linter");
|
|
247
255
|
}
|
|
@@ -284,7 +292,10 @@ function compactReadText(
|
|
|
284
292
|
compaction.sourceCodeFiltering !== "none" &&
|
|
285
293
|
shouldApplyReadSourceFiltering(text, config)
|
|
286
294
|
) {
|
|
287
|
-
const filtered =
|
|
295
|
+
const filtered = normalizeTechniqueResult(
|
|
296
|
+
filterSourceCode(nextText, language, compaction.sourceCodeFiltering),
|
|
297
|
+
nextText,
|
|
298
|
+
);
|
|
288
299
|
if (filtered !== nextText) {
|
|
289
300
|
nextText = filtered;
|
|
290
301
|
techniques.push(`source:${compaction.sourceCodeFiltering}`);
|
|
@@ -328,8 +339,8 @@ function compactGrepText(text: string, config: RtkIntegrationConfig): { text: st
|
|
|
328
339
|
}
|
|
329
340
|
|
|
330
341
|
if (compaction.groupSearchOutput) {
|
|
331
|
-
const grouped = groupSearchResults(nextText);
|
|
332
|
-
if (grouped !==
|
|
342
|
+
const grouped = normalizeTechniqueResult(groupSearchResults(nextText), nextText);
|
|
343
|
+
if (grouped !== nextText) {
|
|
333
344
|
nextText = grouped;
|
|
334
345
|
techniques.push("search");
|
|
335
346
|
}
|
|
@@ -1,157 +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
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
}
|
|
@@ -3,7 +3,12 @@ import { join } from "node:path";
|
|
|
3
3
|
import { splitLeadingEnvAssignments } from "./shell-env-prefix.js";
|
|
4
4
|
|
|
5
5
|
const RTK_DB_PATH_ENV_NAME = "RTK_DB_PATH";
|
|
6
|
-
const
|
|
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*(?:;|$))`);
|
|
7
12
|
|
|
8
13
|
function resolveTemporaryDirectory(): string {
|
|
9
14
|
if (process.platform === "win32") {
|
|
@@ -44,11 +49,15 @@ function getTemporaryRtkHistoryDbPath(): string {
|
|
|
44
49
|
|
|
45
50
|
function quoteForShellEnv(value: string): string {
|
|
46
51
|
const normalizedValue = process.platform === "win32" ? value.replace(/\\/g, "/") : value;
|
|
47
|
-
return `
|
|
52
|
+
return `'${normalizedValue.replace(/'/g, `'\\''`)}'`;
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
function hasLeadingRtkDbPathAssignment(command: string): boolean {
|
|
51
|
-
|
|
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
|
+
);
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
export function applyRtkCommandEnvironment(command: string): string {
|
|
@@ -60,5 +69,5 @@ export function applyRtkCommandEnvironment(command: string): string {
|
|
|
60
69
|
return command;
|
|
61
70
|
}
|
|
62
71
|
|
|
63
|
-
return
|
|
72
|
+
return `export ${RTK_DB_PATH_ENV_NAME}=${quoteForShellEnv(getTemporaryRtkHistoryDbPath())}; ${command}`;
|
|
64
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
|
+
}
|