centaurus-cli 2.7.2 → 2.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/dist/cli-adapter.d.ts +8 -0
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +104 -37
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/mcp-config-manager.d.ts +53 -0
- package/dist/config/mcp-config-manager.d.ts.map +1 -0
- package/dist/config/mcp-config-manager.js +210 -0
- package/dist/config/mcp-config-manager.js.map +1 -0
- package/dist/config/slash-commands.d.ts +14 -0
- package/dist/config/slash-commands.d.ts.map +1 -0
- package/dist/config/slash-commands.js +65 -0
- package/dist/config/slash-commands.js.map +1 -0
- package/dist/context/handlers/wsl-handler.d.ts.map +1 -1
- package/dist/context/handlers/wsl-handler.js +2 -1
- package/dist/context/handlers/wsl-handler.js.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-command-handler.d.ts +16 -0
- package/dist/mcp/mcp-command-handler.d.ts.map +1 -0
- package/dist/mcp/mcp-command-handler.js +196 -0
- package/dist/mcp/mcp-command-handler.js.map +1 -0
- package/dist/mcp/mcp-server-manager.d.ts +30 -0
- package/dist/mcp/mcp-server-manager.d.ts.map +1 -0
- package/dist/mcp/mcp-server-manager.js +165 -0
- package/dist/mcp/mcp-server-manager.js.map +1 -0
- package/dist/mcp/mcp-tool-wrapper.d.ts +12 -0
- package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -0
- package/dist/mcp/mcp-tool-wrapper.js +57 -0
- package/dist/mcp/mcp-tool-wrapper.js.map +1 -0
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +20 -2
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/file-ops.d.ts.map +1 -1
- package/dist/tools/file-ops.js +18 -10
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/ui/components/App.d.ts +4 -0
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +235 -138
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/FileCreationPreview.d.ts +8 -0
- package/dist/ui/components/FileCreationPreview.d.ts.map +1 -0
- package/dist/ui/components/FileCreationPreview.js +63 -0
- package/dist/ui/components/FileCreationPreview.js.map +1 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +132 -4
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.d.ts +3 -1
- package/dist/ui/components/InteractiveShell.d.ts.map +1 -1
- package/dist/ui/components/InteractiveShell.js +46 -89
- package/dist/ui/components/InteractiveShell.js.map +1 -1
- package/dist/ui/components/MarkdownRenderer.d.ts.map +1 -1
- package/dist/ui/components/MarkdownRenderer.js +16 -34
- package/dist/ui/components/MarkdownRenderer.js.map +1 -1
- package/dist/ui/components/MessageDisplay.d.ts.map +1 -1
- package/dist/ui/components/MessageDisplay.js +10 -2
- package/dist/ui/components/MessageDisplay.js.map +1 -1
- package/dist/ui/components/SlashCommandAutocomplete.d.ts +11 -0
- package/dist/ui/components/SlashCommandAutocomplete.d.ts.map +1 -0
- package/dist/ui/components/SlashCommandAutocomplete.js +15 -0
- package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -0
- package/dist/ui/components/StreamingMessageDisplay.d.ts.map +1 -1
- package/dist/ui/components/StreamingMessageDisplay.js +5 -5
- package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.js +96 -32
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/ToolExecutionStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionStatus.js +28 -1
- package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
- package/dist/utils/editor-utils.d.ts +50 -0
- package/dist/utils/editor-utils.d.ts.map +1 -0
- package/dist/utils/editor-utils.js +501 -0
- package/dist/utils/editor-utils.js.map +1 -0
- package/dist/utils/input-classifier.d.ts.map +1 -1
- package/dist/utils/input-classifier.js +4 -2
- package/dist/utils/input-classifier.js.map +1 -1
- package/dist/utils/shell.d.ts +34 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +130 -123
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-checker.d.ts +24 -0
- package/dist/utils/syntax-checker.d.ts.map +1 -0
- package/dist/utils/syntax-checker.js +320 -0
- package/dist/utils/syntax-checker.js.map +1 -0
- package/package.json +5 -3
package/dist/utils/shell.js
CHANGED
|
@@ -1,149 +1,156 @@
|
|
|
1
1
|
import * as os from 'os';
|
|
2
|
-
import
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
// Use createRequire for ESM compatibility with native modules
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const nodePty = require('@homebridge/node-pty-prebuilt-multiarch');
|
|
7
|
+
/**
|
|
8
|
+
* Check if PTY is available on this system
|
|
9
|
+
*/
|
|
10
|
+
export function isPtyAvailable() {
|
|
11
|
+
return nodePty !== null;
|
|
12
|
+
}
|
|
13
|
+
export function killProcessTree(pid, signal) {
|
|
14
|
+
if (os.platform() === 'win32') {
|
|
15
|
+
exec(`taskkill /pid ${pid} /T /F`, (error, stdout, stderr) => {
|
|
16
|
+
if (error) {
|
|
17
|
+
// Fallback to normal kill if taskkill fails
|
|
18
|
+
try {
|
|
19
|
+
process.kill(pid, signal);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
// Ignore if process already gone
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
// POSIX: Kill process group
|
|
29
|
+
try {
|
|
30
|
+
process.kill(-pid, signal);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
// Fallback to normal kill
|
|
34
|
+
try {
|
|
35
|
+
process.kill(pid, signal);
|
|
36
|
+
}
|
|
37
|
+
catch (e2) {
|
|
38
|
+
// Ignore
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
3
43
|
export function getShellCommand() {
|
|
4
44
|
return os.platform() === 'win32' ? 'powershell.exe' : (process.env.SHELL || '/bin/bash');
|
|
5
45
|
}
|
|
6
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Execute a command using node-pty for full terminal emulation.
|
|
48
|
+
* This provides true PTY support with colors, cursor positioning, and interactive programs.
|
|
49
|
+
*/
|
|
50
|
+
export function executePtyCommand(command, cwd, onData, onExit) {
|
|
7
51
|
const shell = getShellCommand();
|
|
8
|
-
|
|
9
|
-
const args =
|
|
10
|
-
|
|
52
|
+
const isWindows = os.platform() === 'win32';
|
|
53
|
+
const args = isWindows ? ['-Command', command] : ['-c', command];
|
|
54
|
+
// Get initial terminal dimensions
|
|
55
|
+
const cols = process.stdout.columns || 80;
|
|
56
|
+
const rows = process.stdout.rows || 24;
|
|
57
|
+
// Spawn PTY process
|
|
58
|
+
const ptyProcess = nodePty.spawn(shell, args, {
|
|
59
|
+
name: 'xterm-256color',
|
|
60
|
+
cols,
|
|
61
|
+
rows,
|
|
11
62
|
cwd,
|
|
12
|
-
stdio: ['pipe', 'pipe', 'pipe'], // CRITICAL: All pipes must be open
|
|
13
63
|
env: {
|
|
14
64
|
...process.env,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Force Color Output (Many tools check this)
|
|
65
|
+
TERM: 'xterm-256color',
|
|
66
|
+
COLORTERM: 'truecolor',
|
|
18
67
|
FORCE_COLOR: '1',
|
|
19
68
|
CLICOLOR: '1',
|
|
20
|
-
|
|
21
|
-
}
|
|
69
|
+
PYTHONUNBUFFERED: '1',
|
|
70
|
+
},
|
|
22
71
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
72
|
+
let isRunning = true;
|
|
73
|
+
// Stream data as it comes
|
|
74
|
+
ptyProcess.onData((data) => {
|
|
75
|
+
onData(data);
|
|
76
|
+
});
|
|
77
|
+
// Handle exit
|
|
78
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
79
|
+
isRunning = false;
|
|
80
|
+
if (onExit) {
|
|
81
|
+
onExit(exitCode);
|
|
30
82
|
}
|
|
31
|
-
throttleTimer = null;
|
|
32
|
-
};
|
|
33
|
-
const handleData = (data) => {
|
|
34
|
-
buffer += data.toString();
|
|
35
|
-
if (!throttleTimer)
|
|
36
|
-
throttleTimer = setTimeout(flush, 50); // 50ms debounce
|
|
37
|
-
};
|
|
38
|
-
childProcess.stdout?.on('data', handleData);
|
|
39
|
-
childProcess.stderr?.on('data', handleData); // Merge stderr so user sees errors inline
|
|
40
|
-
childProcess.on('close', (code) => {
|
|
41
|
-
if (throttleTimer)
|
|
42
|
-
clearTimeout(throttleTimer);
|
|
43
|
-
flush();
|
|
44
|
-
if (onExit)
|
|
45
|
-
onExit(code ?? 0);
|
|
46
83
|
});
|
|
47
84
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
85
|
+
write: (data) => {
|
|
86
|
+
if (isRunning) {
|
|
87
|
+
ptyProcess.write(data);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
resize: (cols, rows) => {
|
|
91
|
+
if (isRunning) {
|
|
92
|
+
ptyProcess.resize(cols, rows);
|
|
53
93
|
}
|
|
54
94
|
},
|
|
55
95
|
kill: () => {
|
|
56
|
-
|
|
57
|
-
|
|
96
|
+
if (isRunning) {
|
|
97
|
+
ptyProcess.kill();
|
|
98
|
+
isRunning = false;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
isRunning: () => isRunning,
|
|
102
|
+
pid: ptyProcess.pid,
|
|
58
103
|
};
|
|
59
104
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (stdoutBuffer) {
|
|
84
|
-
onData(stdoutBuffer, 'stdout');
|
|
85
|
-
stdoutBuffer = '';
|
|
86
|
-
}
|
|
87
|
-
if (stderrBuffer) {
|
|
88
|
-
onData(stderrBuffer, 'stderr');
|
|
89
|
-
stderrBuffer = '';
|
|
90
|
-
}
|
|
105
|
+
/**
|
|
106
|
+
* Execute a command interactively using PTY.
|
|
107
|
+
* Now exclusively uses node-pty for production-grade terminal experience.
|
|
108
|
+
*/
|
|
109
|
+
export function executeCommandInteractive(command, cwd, onData, onExit) {
|
|
110
|
+
const ptyProc = executePtyCommand(command, cwd, (chunk) => onData(chunk, 'stdout'), onExit);
|
|
111
|
+
return {
|
|
112
|
+
process: null,
|
|
113
|
+
ptyProcess: ptyProc,
|
|
114
|
+
write: (input) => {
|
|
115
|
+
ptyProc.write(input);
|
|
116
|
+
},
|
|
117
|
+
kill: () => ptyProc.kill(),
|
|
118
|
+
signal: (signal) => {
|
|
119
|
+
// For PTY, send control characters for signals
|
|
120
|
+
if (signal === 'SIGINT') {
|
|
121
|
+
ptyProc.write('\x03'); // Ctrl+C
|
|
122
|
+
// Also try to kill after a short delay if it doesn't respond
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
if (ptyProc.isRunning()) {
|
|
125
|
+
ptyProc.kill();
|
|
126
|
+
}
|
|
127
|
+
}, 500);
|
|
91
128
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// Capture stdout with throttled streaming
|
|
95
|
-
childProcess.stdout?.on('data', (data) => {
|
|
96
|
-
const chunk = data.toString();
|
|
97
|
-
stdout += chunk;
|
|
98
|
-
// Buffer the chunk
|
|
99
|
-
stdoutBuffer += chunk;
|
|
100
|
-
// Set throttle timer if not already running
|
|
101
|
-
if (!throttleTimer && onData) {
|
|
102
|
-
throttleTimer = setTimeout(flush, 50); // 50ms throttle
|
|
129
|
+
else if (signal === 'SIGTERM') {
|
|
130
|
+
ptyProc.kill();
|
|
103
131
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
childProcess.stderr?.on('data', (data) => {
|
|
107
|
-
const chunk = data.toString();
|
|
108
|
-
stderr += chunk;
|
|
109
|
-
// Buffer the chunk
|
|
110
|
-
stderrBuffer += chunk;
|
|
111
|
-
// Set throttle timer if not already running
|
|
112
|
-
if (!throttleTimer && onData) {
|
|
113
|
-
throttleTimer = setTimeout(flush, 50); // 50ms throttle
|
|
132
|
+
else {
|
|
133
|
+
ptyProc.kill();
|
|
114
134
|
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
stderr,
|
|
128
|
-
exitCode: exitCode ?? 0,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
// Handle process errors
|
|
133
|
-
childProcess.on('error', (error) => {
|
|
134
|
-
if (!isResolved) {
|
|
135
|
-
isResolved = true;
|
|
136
|
-
// Clear throttle timer and flush remaining data
|
|
137
|
-
if (throttleTimer) {
|
|
138
|
-
clearTimeout(throttleTimer);
|
|
139
|
-
}
|
|
140
|
-
flush();
|
|
141
|
-
resolve({
|
|
142
|
-
stdout,
|
|
143
|
-
stderr: stderr || error.message,
|
|
144
|
-
exitCode: 1,
|
|
145
|
-
});
|
|
135
|
+
},
|
|
136
|
+
resize: (cols, rows) => ptyProc.resize(cols, rows),
|
|
137
|
+
isPty: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export async function executeCommand(command, cwd, onData) {
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
let output = '';
|
|
143
|
+
const proc = executeCommandInteractive(command, cwd, (chunk) => {
|
|
144
|
+
output += chunk;
|
|
145
|
+
if (onData) {
|
|
146
|
+
onData(chunk, 'stdout');
|
|
146
147
|
}
|
|
148
|
+
}, (exitCode) => {
|
|
149
|
+
resolve({
|
|
150
|
+
stdout: output,
|
|
151
|
+
stderr: '',
|
|
152
|
+
exitCode,
|
|
153
|
+
});
|
|
147
154
|
});
|
|
148
155
|
});
|
|
149
156
|
}
|
package/dist/utils/shell.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a syntax check operation
|
|
3
|
+
*/
|
|
4
|
+
export interface SyntaxCheckResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
language: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
skipped?: boolean;
|
|
9
|
+
skipReason?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Detect language from file path
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectLanguage(filePath: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Main syntax check function
|
|
17
|
+
* Checks the syntax of a file based on its extension
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkSyntax(filePath: string, content?: string): Promise<SyntaxCheckResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Format syntax check result as a human-readable string
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatSyntaxCheckResult(result: SyntaxCheckResult): string;
|
|
24
|
+
//# sourceMappingURL=syntax-checker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syntax-checker.d.ts","sourceRoot":"","sources":["../../src/utils/syntax-checker.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AA6ED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBvD;AA8KD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA6DhG;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAUzE"}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import * as vm from 'vm';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import * as ts from 'typescript';
|
|
6
|
+
/**
|
|
7
|
+
* File extension to language mapping
|
|
8
|
+
*/
|
|
9
|
+
const LANGUAGE_MAP = {
|
|
10
|
+
// JavaScript
|
|
11
|
+
'.js': 'javascript',
|
|
12
|
+
'.mjs': 'javascript',
|
|
13
|
+
'.cjs': 'javascript',
|
|
14
|
+
'.jsx': 'javascript',
|
|
15
|
+
// TypeScript
|
|
16
|
+
'.ts': 'typescript',
|
|
17
|
+
'.tsx': 'typescript',
|
|
18
|
+
'.mts': 'typescript',
|
|
19
|
+
'.cts': 'typescript',
|
|
20
|
+
// Python
|
|
21
|
+
'.py': 'python',
|
|
22
|
+
'.pyw': 'python',
|
|
23
|
+
// JSON
|
|
24
|
+
'.json': 'json',
|
|
25
|
+
'.jsonc': 'json',
|
|
26
|
+
// Skip syntax checking for these (text/markup/config)
|
|
27
|
+
'.txt': 'text',
|
|
28
|
+
'.md': 'markdown',
|
|
29
|
+
'.markdown': 'markdown',
|
|
30
|
+
'.log': 'log',
|
|
31
|
+
'.csv': 'csv',
|
|
32
|
+
'.html': 'html',
|
|
33
|
+
'.htm': 'html',
|
|
34
|
+
'.css': 'css',
|
|
35
|
+
'.scss': 'scss',
|
|
36
|
+
'.sass': 'sass',
|
|
37
|
+
'.less': 'less',
|
|
38
|
+
'.xml': 'xml',
|
|
39
|
+
'.yaml': 'yaml',
|
|
40
|
+
'.yml': 'yaml',
|
|
41
|
+
'.toml': 'toml',
|
|
42
|
+
'.ini': 'ini',
|
|
43
|
+
'.cfg': 'config',
|
|
44
|
+
'.conf': 'config',
|
|
45
|
+
'.env': 'env',
|
|
46
|
+
'.gitignore': 'gitignore',
|
|
47
|
+
'.dockerignore': 'dockerignore',
|
|
48
|
+
'.editorconfig': 'config',
|
|
49
|
+
'.prettierrc': 'config',
|
|
50
|
+
'.eslintrc': 'config',
|
|
51
|
+
'.babelrc': 'config',
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Languages that should skip syntax checking
|
|
55
|
+
*/
|
|
56
|
+
const SKIP_SYNTAX_CHECK = new Set([
|
|
57
|
+
'text',
|
|
58
|
+
'markdown',
|
|
59
|
+
'log',
|
|
60
|
+
'csv',
|
|
61
|
+
'html',
|
|
62
|
+
'css',
|
|
63
|
+
'scss',
|
|
64
|
+
'sass',
|
|
65
|
+
'less',
|
|
66
|
+
'xml',
|
|
67
|
+
'yaml',
|
|
68
|
+
'toml',
|
|
69
|
+
'ini',
|
|
70
|
+
'config',
|
|
71
|
+
'env',
|
|
72
|
+
'gitignore',
|
|
73
|
+
'dockerignore',
|
|
74
|
+
]);
|
|
75
|
+
/**
|
|
76
|
+
* Detect language from file path
|
|
77
|
+
*/
|
|
78
|
+
export function detectLanguage(filePath) {
|
|
79
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
80
|
+
const basename = path.basename(filePath).toLowerCase();
|
|
81
|
+
// Check for special filenames without extensions
|
|
82
|
+
if (!ext) {
|
|
83
|
+
// Common config files without extensions
|
|
84
|
+
if (basename === 'dockerfile')
|
|
85
|
+
return 'dockerfile';
|
|
86
|
+
if (basename === 'makefile')
|
|
87
|
+
return 'makefile';
|
|
88
|
+
if (basename === '.gitignore')
|
|
89
|
+
return 'gitignore';
|
|
90
|
+
if (basename === '.dockerignore')
|
|
91
|
+
return 'dockerignore';
|
|
92
|
+
if (basename === '.env')
|
|
93
|
+
return 'env';
|
|
94
|
+
return 'unknown';
|
|
95
|
+
}
|
|
96
|
+
return LANGUAGE_MAP[ext] || 'unknown';
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Check JavaScript syntax using vm.Script
|
|
100
|
+
*/
|
|
101
|
+
function checkJavaScriptSyntax(content) {
|
|
102
|
+
try {
|
|
103
|
+
// Use vm.Script to parse and check syntax without execution
|
|
104
|
+
new vm.Script(content, { filename: 'syntax-check.js' });
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
language: 'JavaScript',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const err = error;
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
language: 'JavaScript',
|
|
115
|
+
error: err.message,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check TypeScript syntax using the TypeScript compiler API
|
|
121
|
+
*/
|
|
122
|
+
function checkTypeScriptSyntax(content, filePath) {
|
|
123
|
+
try {
|
|
124
|
+
const isTsx = filePath.endsWith('.tsx');
|
|
125
|
+
const compilerOptions = {
|
|
126
|
+
noEmit: true,
|
|
127
|
+
target: ts.ScriptTarget.ESNext,
|
|
128
|
+
module: ts.ModuleKind.ESNext,
|
|
129
|
+
jsx: isTsx ? ts.JsxEmit.React : undefined,
|
|
130
|
+
strict: false, // Don't enforce strict mode for syntax checking
|
|
131
|
+
skipLibCheck: true,
|
|
132
|
+
allowJs: true,
|
|
133
|
+
};
|
|
134
|
+
// Create a simple in-memory source file
|
|
135
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.ESNext, true, isTsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
136
|
+
// Check for parse errors (syntax errors)
|
|
137
|
+
const parseErrors = [];
|
|
138
|
+
// Walk through the source file to find syntax errors
|
|
139
|
+
// TypeScript's createSourceFile doesn't throw on syntax errors,
|
|
140
|
+
// but we can check for diagnostic messages
|
|
141
|
+
const parseDiagnostics = sourceFile.parseDiagnostics;
|
|
142
|
+
if (parseDiagnostics && parseDiagnostics.length > 0) {
|
|
143
|
+
for (const diag of parseDiagnostics) {
|
|
144
|
+
const message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
|
|
145
|
+
const lineAndChar = sourceFile.getLineAndCharacterOfPosition(diag.start || 0);
|
|
146
|
+
parseErrors.push(`Line ${lineAndChar.line + 1}: ${message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (parseErrors.length > 0) {
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
language: 'TypeScript',
|
|
153
|
+
error: parseErrors.join('\n'),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
language: 'TypeScript',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const err = error;
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
language: 'TypeScript',
|
|
166
|
+
error: err.message,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check Python syntax using the py_compile module
|
|
172
|
+
*/
|
|
173
|
+
async function checkPythonSyntax(filePath) {
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
// Use python -m py_compile to check syntax
|
|
176
|
+
const pythonCommands = process.platform === 'win32'
|
|
177
|
+
? ['python', 'python3', 'py']
|
|
178
|
+
: ['python3', 'python'];
|
|
179
|
+
let attemptIndex = 0;
|
|
180
|
+
const tryNextCommand = () => {
|
|
181
|
+
if (attemptIndex >= pythonCommands.length) {
|
|
182
|
+
// No Python found
|
|
183
|
+
resolve({
|
|
184
|
+
success: true,
|
|
185
|
+
language: 'Python',
|
|
186
|
+
skipped: true,
|
|
187
|
+
skipReason: 'Python interpreter not found. Syntax check skipped.',
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const pythonCmd = pythonCommands[attemptIndex];
|
|
192
|
+
attemptIndex++;
|
|
193
|
+
const proc = spawn(pythonCmd, ['-m', 'py_compile', filePath], {
|
|
194
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
195
|
+
timeout: 5000, // 5 second timeout
|
|
196
|
+
});
|
|
197
|
+
let stderr = '';
|
|
198
|
+
proc.stderr.on('data', (data) => {
|
|
199
|
+
stderr += data.toString();
|
|
200
|
+
});
|
|
201
|
+
proc.on('error', () => {
|
|
202
|
+
// This python command failed, try next
|
|
203
|
+
tryNextCommand();
|
|
204
|
+
});
|
|
205
|
+
proc.on('close', (code) => {
|
|
206
|
+
if (code === 0) {
|
|
207
|
+
resolve({
|
|
208
|
+
success: true,
|
|
209
|
+
language: 'Python',
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// Parse the error message
|
|
214
|
+
const errorLines = stderr.trim().split('\n');
|
|
215
|
+
const relevantError = errorLines.length > 0
|
|
216
|
+
? errorLines[errorLines.length - 1]
|
|
217
|
+
: 'Syntax error detected';
|
|
218
|
+
resolve({
|
|
219
|
+
success: false,
|
|
220
|
+
language: 'Python',
|
|
221
|
+
error: relevantError,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
tryNextCommand();
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Check JSON syntax using JSON.parse
|
|
231
|
+
*/
|
|
232
|
+
function checkJsonSyntax(content) {
|
|
233
|
+
try {
|
|
234
|
+
JSON.parse(content);
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
language: 'JSON',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
const err = error;
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
language: 'JSON',
|
|
245
|
+
error: err.message,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Main syntax check function
|
|
251
|
+
* Checks the syntax of a file based on its extension
|
|
252
|
+
*/
|
|
253
|
+
export async function checkSyntax(filePath, content) {
|
|
254
|
+
const language = detectLanguage(filePath);
|
|
255
|
+
// Skip syntax checking for text/config files
|
|
256
|
+
if (SKIP_SYNTAX_CHECK.has(language)) {
|
|
257
|
+
return {
|
|
258
|
+
success: true,
|
|
259
|
+
language: language.charAt(0).toUpperCase() + language.slice(1),
|
|
260
|
+
skipped: true,
|
|
261
|
+
skipReason: `Syntax check skipped for ${language} files`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// Skip unknown file types
|
|
265
|
+
if (language === 'unknown') {
|
|
266
|
+
return {
|
|
267
|
+
success: true,
|
|
268
|
+
language: 'Unknown',
|
|
269
|
+
skipped: true,
|
|
270
|
+
skipReason: 'Syntax check skipped for unknown file type',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// Read content from file if not provided
|
|
274
|
+
let fileContent = content;
|
|
275
|
+
if (!fileContent) {
|
|
276
|
+
try {
|
|
277
|
+
fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
return {
|
|
281
|
+
success: true,
|
|
282
|
+
language: language.charAt(0).toUpperCase() + language.slice(1),
|
|
283
|
+
skipped: true,
|
|
284
|
+
skipReason: 'Could not read file for syntax check',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Perform language-specific syntax check
|
|
289
|
+
switch (language) {
|
|
290
|
+
case 'javascript':
|
|
291
|
+
return checkJavaScriptSyntax(fileContent);
|
|
292
|
+
case 'typescript':
|
|
293
|
+
return checkTypeScriptSyntax(fileContent, filePath);
|
|
294
|
+
case 'python':
|
|
295
|
+
// Python requires the file to be on disk for py_compile
|
|
296
|
+
return await checkPythonSyntax(filePath);
|
|
297
|
+
case 'json':
|
|
298
|
+
return checkJsonSyntax(fileContent);
|
|
299
|
+
default:
|
|
300
|
+
return {
|
|
301
|
+
success: true,
|
|
302
|
+
language: language.charAt(0).toUpperCase() + language.slice(1),
|
|
303
|
+
skipped: true,
|
|
304
|
+
skipReason: `Syntax check not implemented for ${language}`,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Format syntax check result as a human-readable string
|
|
310
|
+
*/
|
|
311
|
+
export function formatSyntaxCheckResult(result) {
|
|
312
|
+
if (result.skipped) {
|
|
313
|
+
return `(${result.skipReason})`;
|
|
314
|
+
}
|
|
315
|
+
if (result.success) {
|
|
316
|
+
return `✓ Syntax check passed (${result.language})`;
|
|
317
|
+
}
|
|
318
|
+
return `✗ Syntax error (${result.language}): ${result.error}`;
|
|
319
|
+
}
|
|
320
|
+
//# sourceMappingURL=syntax-checker.js.map
|