pi-rtk-optimizer 0.7.0 → 0.8.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.
- package/CHANGELOG.md +25 -1
- package/README.md +14 -8
- package/package.json +67 -64
- package/src/additional-coverage-test.ts +117 -58
- package/src/command-completions.ts +49 -49
- package/src/command-rewriter-test.ts +187 -118
- package/src/command-rewriter.ts +46 -43
- package/src/config-modal-test.ts +97 -31
- package/src/config-modal.ts +91 -12
- package/src/constants.ts +1 -1
- package/src/index-test.ts +198 -3
- package/src/index.ts +49 -5
- package/src/output-compactor-test.ts +208 -3
- package/src/output-compactor.ts +316 -16
- 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 +126 -90
- 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-shims.d.ts +4 -2
- package/src/types.ts +4 -0
- package/src/windows-command-helpers.ts +92 -16
- package/src/zellij-modal.ts +137 -30
|
@@ -1,90 +1,126 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@
|
|
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
|
-
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { resolveRtkExecutable, type RtkExecutableResolution } from "./rtk-executable-resolver.js";
|
|
3
|
+
|
|
4
|
+
export interface RtkRewriteProviderResult {
|
|
5
|
+
changed: boolean;
|
|
6
|
+
originalCommand: string;
|
|
7
|
+
rewrittenCommand: string;
|
|
8
|
+
exitCode: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
executableResolution?: RtkExecutableResolution;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RtkRewriteProviderOptions {
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
resolverTimeoutMs?: number;
|
|
16
|
+
platform?: typeof process.platform;
|
|
17
|
+
executableResolution?: RtkExecutableResolution;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isAlreadyRtk(command: string): boolean {
|
|
21
|
+
const trimmed = command.trimStart();
|
|
22
|
+
return trimmed === "rtk" || trimmed.startsWith("rtk ");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeOptions(optionsOrTimeout: number | RtkRewriteProviderOptions): RtkRewriteProviderOptions {
|
|
26
|
+
if (typeof optionsOrTimeout === "number") {
|
|
27
|
+
return { timeoutMs: optionsOrTimeout };
|
|
28
|
+
}
|
|
29
|
+
return optionsOrTimeout;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function resolveRtkRewrite(
|
|
33
|
+
pi: ExtensionAPI,
|
|
34
|
+
command: string,
|
|
35
|
+
optionsOrTimeout: number | RtkRewriteProviderOptions = {},
|
|
36
|
+
): Promise<RtkRewriteProviderResult> {
|
|
37
|
+
const options = normalizeOptions(optionsOrTimeout);
|
|
38
|
+
const timeoutMs = options.timeoutMs ?? 3000;
|
|
39
|
+
|
|
40
|
+
if (!command || !command.trim()) {
|
|
41
|
+
return { changed: false, originalCommand: command, rewrittenCommand: command, exitCode: 1 };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isAlreadyRtk(command)) {
|
|
45
|
+
return { changed: false, originalCommand: command, rewrittenCommand: command, exitCode: 1 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const executableResolution =
|
|
50
|
+
options.executableResolution ??
|
|
51
|
+
(await resolveRtkExecutable(pi, {
|
|
52
|
+
platform: options.platform,
|
|
53
|
+
timeoutMs: options.resolverTimeoutMs,
|
|
54
|
+
}));
|
|
55
|
+
const result = await pi.exec(executableResolution.command, ["rewrite", command], { timeout: timeoutMs });
|
|
56
|
+
|
|
57
|
+
if (result.code === 1) {
|
|
58
|
+
return {
|
|
59
|
+
changed: false,
|
|
60
|
+
originalCommand: command,
|
|
61
|
+
rewrittenCommand: command,
|
|
62
|
+
exitCode: 1,
|
|
63
|
+
executableResolution,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (result.code === 2) {
|
|
68
|
+
return {
|
|
69
|
+
changed: false,
|
|
70
|
+
originalCommand: command,
|
|
71
|
+
rewrittenCommand: command,
|
|
72
|
+
exitCode: 2,
|
|
73
|
+
error: result.stderr?.trim() || "rtk denied rewrite",
|
|
74
|
+
executableResolution,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (result.code === 0 || result.code === 3) {
|
|
79
|
+
const rewritten = result.stdout?.trim();
|
|
80
|
+
if (!rewritten) {
|
|
81
|
+
return {
|
|
82
|
+
changed: false,
|
|
83
|
+
originalCommand: command,
|
|
84
|
+
rewrittenCommand: command,
|
|
85
|
+
exitCode: result.code,
|
|
86
|
+
error: "rtk returned empty output",
|
|
87
|
+
executableResolution,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (rewritten === command) {
|
|
91
|
+
return {
|
|
92
|
+
changed: false,
|
|
93
|
+
originalCommand: command,
|
|
94
|
+
rewrittenCommand: command,
|
|
95
|
+
exitCode: result.code,
|
|
96
|
+
executableResolution,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
changed: true,
|
|
101
|
+
originalCommand: command,
|
|
102
|
+
rewrittenCommand: rewritten,
|
|
103
|
+
exitCode: result.code,
|
|
104
|
+
executableResolution,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
changed: false,
|
|
110
|
+
originalCommand: command,
|
|
111
|
+
rewrittenCommand: command,
|
|
112
|
+
exitCode: result.code,
|
|
113
|
+
error: `unexpected exit code ${result.code}`,
|
|
114
|
+
executableResolution,
|
|
115
|
+
};
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
+
return {
|
|
119
|
+
changed: false,
|
|
120
|
+
originalCommand: command,
|
|
121
|
+
rewrittenCommand: command,
|
|
122
|
+
exitCode: -1,
|
|
123
|
+
error: message,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/shell-env-prefix.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
const
|
|
1
|
+
const SINGLE_QUOTED_SHELL_VALUE_PATTERN = "'(?:'\\\\''|[^'])*'";
|
|
2
|
+
const ENV_ASSIGNMENT_VALUE_PATTERN = `(?:"[^"]*"|${SINGLE_QUOTED_SHELL_VALUE_PATTERN}|[^\\s]+)`;
|
|
3
|
+
const LEADING_ENV_ASSIGNMENT_PATTERN = new RegExp(
|
|
4
|
+
`^((?:[A-Za-z_][A-Za-z0-9_]*=${ENV_ASSIGNMENT_VALUE_PATTERN}\\s+)*)`,
|
|
5
|
+
);
|
|
2
6
|
|
|
3
7
|
export interface LeadingEnvAssignmentSplit {
|
|
4
8
|
envPrefix: string;
|
package/src/test-helpers.ts
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
import { DEFAULT_RTK_INTEGRATION_CONFIG, type RtkIntegrationConfig } from "./types.ts";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { DEFAULT_RTK_INTEGRATION_CONFIG, type RtkIntegrationConfig } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
type TestResult = void | Promise<void>;
|
|
4
|
+
|
|
5
|
+
function isPromiseLike(value: TestResult): value is Promise<void> {
|
|
6
|
+
return Boolean(value && typeof (value as Promise<void>).then === "function");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function runTest(name: string, testFn: () => TestResult): TestResult {
|
|
10
|
+
const result = testFn();
|
|
11
|
+
if (!isPromiseLike(result)) {
|
|
12
|
+
console.log(`[PASS] ${name}`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return result.then(() => {
|
|
17
|
+
console.log(`[PASS] ${name}`);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function cloneDefaultConfig(): RtkIntegrationConfig {
|
|
22
|
+
return structuredClone(DEFAULT_RTK_INTEGRATION_CONFIG);
|
|
23
|
+
}
|
|
@@ -1,69 +1,80 @@
|
|
|
1
|
-
import { toRecord } from "./record-utils.js";
|
|
2
|
-
import { sanitizeRtkEmojiOutput, stripAnsiFast, stripRtkHookWarnings } from "./techniques/index.js";
|
|
3
|
-
|
|
4
|
-
interface ToolResultTextBlock {
|
|
5
|
-
type: string;
|
|
6
|
-
text?: string;
|
|
7
|
-
[key: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
nextText =
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
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
|
-
|
|
1
|
+
import { toRecord } from "./record-utils.js";
|
|
2
|
+
import { sanitizeRtkEmojiOutput, stripAnsiFast, stripRtkHookWarnings } from "./techniques/index.js";
|
|
3
|
+
|
|
4
|
+
interface ToolResultTextBlock {
|
|
5
|
+
type: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface StreamingBashExecutionSanitizationResult {
|
|
11
|
+
changed: boolean;
|
|
12
|
+
result: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function sanitizeStreamingBashText(text: string, command: string | undefined | null): string {
|
|
16
|
+
let nextText = stripAnsiFast(text);
|
|
17
|
+
|
|
18
|
+
const withoutRtkHookWarnings = stripRtkHookWarnings(nextText, command);
|
|
19
|
+
if (withoutRtkHookWarnings !== null) {
|
|
20
|
+
nextText = withoutRtkHookWarnings;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const withoutRtkEmoji = sanitizeRtkEmojiOutput(nextText, command);
|
|
24
|
+
if (withoutRtkEmoji !== null) {
|
|
25
|
+
nextText = withoutRtkEmoji;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return nextText;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns a sanitized shallow copy of streamed bash result blocks before the
|
|
33
|
+
* TUI renders them so RTK self-diagnostics never flash in partial or final
|
|
34
|
+
* tool output. The input object is not mutated.
|
|
35
|
+
*/
|
|
36
|
+
export function sanitizeStreamingBashExecutionResult(
|
|
37
|
+
result: unknown,
|
|
38
|
+
command: string | undefined | null,
|
|
39
|
+
): StreamingBashExecutionSanitizationResult {
|
|
40
|
+
const resultRecord = toRecord(result);
|
|
41
|
+
const sourceContent = Array.isArray(resultRecord.content) ? resultRecord.content : null;
|
|
42
|
+
if (!sourceContent || sourceContent.length === 0) {
|
|
43
|
+
return { changed: false, result };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let changed = false;
|
|
47
|
+
const nextContent = sourceContent.map((block) => {
|
|
48
|
+
if (!block || typeof block !== "object" || Array.isArray(block)) {
|
|
49
|
+
return block;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const contentBlock = block as ToolResultTextBlock;
|
|
53
|
+
if (contentBlock.type !== "text" || typeof contentBlock.text !== "string") {
|
|
54
|
+
return block;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const sanitizedText = sanitizeStreamingBashText(contentBlock.text, command);
|
|
58
|
+
if (sanitizedText === contentBlock.text) {
|
|
59
|
+
return block;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
changed = true;
|
|
63
|
+
return {
|
|
64
|
+
...contentBlock,
|
|
65
|
+
text: sanitizedText,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!changed) {
|
|
70
|
+
return { changed: false, result };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
changed: true,
|
|
75
|
+
result: {
|
|
76
|
+
...resultRecord,
|
|
77
|
+
content: nextContent,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
package/src/types-shims.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare module "@
|
|
1
|
+
declare module "@earendil-works/pi-tui" {
|
|
2
2
|
export interface SettingItem {
|
|
3
3
|
id: string;
|
|
4
4
|
label: string;
|
|
@@ -27,6 +27,8 @@ declare module "@mariozechner/pi-tui" {
|
|
|
27
27
|
|
|
28
28
|
export class SettingsList {
|
|
29
29
|
constructor(...args: unknown[]);
|
|
30
|
+
render(width: number): string[];
|
|
31
|
+
invalidate(): void;
|
|
30
32
|
handleInput(data: string): void;
|
|
31
33
|
updateValue(id: string, value: string): void;
|
|
32
34
|
}
|
|
@@ -43,7 +45,7 @@ declare module "@mariozechner/pi-tui" {
|
|
|
43
45
|
export function visibleWidth(text: string): number;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
declare module "@
|
|
48
|
+
declare module "@earendil-works/pi-coding-agent" {
|
|
47
49
|
interface UiLike {
|
|
48
50
|
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
49
51
|
custom<T>(
|
package/src/types.ts
CHANGED
|
@@ -87,5 +87,9 @@ export interface RuntimeStatus {
|
|
|
87
87
|
rtkAvailable: boolean;
|
|
88
88
|
lastCheckedAt?: number;
|
|
89
89
|
lastError?: string;
|
|
90
|
+
rtkExecutablePath?: string;
|
|
91
|
+
rtkExecutableCommand?: string;
|
|
92
|
+
rtkExecutableResolver?: string;
|
|
93
|
+
rtkExecutableResolutionWarning?: string;
|
|
90
94
|
}
|
|
91
95
|
|
|
@@ -3,6 +3,12 @@ interface WindowsBashCompatibilityResult {
|
|
|
3
3
|
applied: string[];
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
interface LeadingCdSlashDParse {
|
|
7
|
+
rawPath: string;
|
|
8
|
+
operator: string;
|
|
9
|
+
tail: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
const PYTHON_UTF8_ENV_PREFIX = "PYTHONIOENCODING=utf-8";
|
|
7
13
|
|
|
8
14
|
function normalizeWindowsPathForBash(rawPath: string): string {
|
|
@@ -20,29 +26,96 @@ function quoteForBash(value: string): string {
|
|
|
20
26
|
return `"${escaped}"`;
|
|
21
27
|
}
|
|
22
28
|
|
|
29
|
+
function parseLeadingCdSlashD(command: string): LeadingCdSlashDParse | null {
|
|
30
|
+
const prefixMatch = command.match(/^\s*cd\s+\/d\s+/i);
|
|
31
|
+
if (!prefixMatch) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const pathStart = prefixMatch[0].length;
|
|
36
|
+
let quote: '"' | "'" | null = null;
|
|
37
|
+
let escaped = false;
|
|
38
|
+
|
|
39
|
+
for (let index = pathStart; index < command.length; index += 1) {
|
|
40
|
+
const character = command[index] ?? "";
|
|
41
|
+
const nextCharacter = command[index + 1] ?? "";
|
|
42
|
+
|
|
43
|
+
if (escaped) {
|
|
44
|
+
escaped = false;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (quote !== null) {
|
|
49
|
+
if (character === "\\" && quote !== "'") {
|
|
50
|
+
escaped = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (character === quote) {
|
|
54
|
+
quote = null;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (character === "\\") {
|
|
60
|
+
escaped = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (character === '"' || character === "'") {
|
|
65
|
+
quote = character;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (character === "&" && nextCharacter === "&") {
|
|
70
|
+
return {
|
|
71
|
+
rawPath: command.slice(pathStart, index),
|
|
72
|
+
operator: "&&",
|
|
73
|
+
tail: command.slice(index + 2),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (character === "|" && nextCharacter === "|") {
|
|
78
|
+
return {
|
|
79
|
+
rawPath: command.slice(pathStart, index),
|
|
80
|
+
operator: "||",
|
|
81
|
+
tail: command.slice(index + 2),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (character === "|" || character === ";") {
|
|
86
|
+
return {
|
|
87
|
+
rawPath: command.slice(pathStart, index),
|
|
88
|
+
operator: character,
|
|
89
|
+
tail: command.slice(index + 1),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
rawPath: command.slice(pathStart),
|
|
96
|
+
operator: "",
|
|
97
|
+
tail: "",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
23
101
|
function rewriteLeadingCdSlashD(command: string): { command: string; changed: boolean } {
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
const tail = withTailMatch[2] ?? "";
|
|
28
|
-
const normalizedPath = quoteForBash(normalizeWindowsPathForBash(rawPath));
|
|
29
|
-
return {
|
|
30
|
-
command: `cd ${normalizedPath} && ${tail}`,
|
|
31
|
-
changed: true,
|
|
32
|
-
};
|
|
102
|
+
const parsed = parseLeadingCdSlashD(command);
|
|
103
|
+
if (!parsed) {
|
|
104
|
+
return { command, changed: false };
|
|
33
105
|
}
|
|
34
106
|
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
const rawPath = onlyCdMatch[1] ?? "";
|
|
38
|
-
const normalizedPath = quoteForBash(normalizeWindowsPathForBash(rawPath));
|
|
107
|
+
const normalizedPath = quoteForBash(normalizeWindowsPathForBash(parsed.rawPath));
|
|
108
|
+
if (!parsed.operator) {
|
|
39
109
|
return {
|
|
40
110
|
command: `cd ${normalizedPath}`,
|
|
41
111
|
changed: true,
|
|
42
112
|
};
|
|
43
113
|
}
|
|
44
114
|
|
|
45
|
-
return {
|
|
115
|
+
return {
|
|
116
|
+
command: `cd ${normalizedPath} ${parsed.operator} ${parsed.tail.trimStart()}`,
|
|
117
|
+
changed: true,
|
|
118
|
+
};
|
|
46
119
|
}
|
|
47
120
|
|
|
48
121
|
function ensurePythonUtf8(command: string): { command: string; changed: boolean } {
|
|
@@ -60,8 +133,11 @@ function ensurePythonUtf8(command: string): { command: string; changed: boolean
|
|
|
60
133
|
};
|
|
61
134
|
}
|
|
62
135
|
|
|
63
|
-
export function applyWindowsBashCompatibilityFixes(
|
|
64
|
-
|
|
136
|
+
export function applyWindowsBashCompatibilityFixes(
|
|
137
|
+
command: string,
|
|
138
|
+
platform: string = process.platform,
|
|
139
|
+
): WindowsBashCompatibilityResult {
|
|
140
|
+
if (platform !== "win32") {
|
|
65
141
|
return { command, applied: [] };
|
|
66
142
|
}
|
|
67
143
|
|