@codesys/mcp-toolkit 1.0.7 → 1.0.9
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/codesys_interop.js +88 -71
- package/package.json +1 -1
package/dist/codesys_interop.js
CHANGED
|
@@ -43,7 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.executeCodesysScript = executeCodesysScript;
|
|
46
|
-
const child_process_1 = require("child_process");
|
|
46
|
+
const child_process_1 = require("child_process"); // Still using spawn
|
|
47
47
|
const promises_1 = require("fs/promises");
|
|
48
48
|
const path = __importStar(require("path"));
|
|
49
49
|
const os = __importStar(require("os"));
|
|
@@ -52,6 +52,7 @@ const SCRIPT_SUCCESS_MARKER = 'SCRIPT_SUCCESS';
|
|
|
52
52
|
const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
|
|
53
53
|
/**
|
|
54
54
|
* Executes a CODESYS Python script using the command line interface.
|
|
55
|
+
* Tries using spawn with shell:true and explicit quoting for cmd.exe.
|
|
55
56
|
*
|
|
56
57
|
* @param scriptContent The Python script code to execute.
|
|
57
58
|
* @param codesysExePath The full path to the CODESYS.exe executable.
|
|
@@ -60,69 +61,84 @@ const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
|
|
|
60
61
|
*/
|
|
61
62
|
function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName) {
|
|
62
63
|
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
-
// Validate arguments passed to the function
|
|
64
64
|
if (!codesysExePath) {
|
|
65
65
|
throw new Error('CODESYS executable path was not provided to executeCodesysScript.');
|
|
66
66
|
}
|
|
67
67
|
if (!codesysProfileName) {
|
|
68
68
|
throw new Error('CODESYS profile name was not provided to executeCodesysScript.');
|
|
69
69
|
}
|
|
70
|
-
// Create a temporary file for the script
|
|
71
70
|
const tempDir = os.tmpdir();
|
|
72
|
-
const tempFileName = `codesys_script_${Date.now()}_${Math.random().toString(36).substring(
|
|
71
|
+
const tempFileName = `codesys_script_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
|
|
73
72
|
const tempFilePath = path.join(tempDir, tempFileName);
|
|
74
73
|
let output = '';
|
|
75
74
|
let success = false;
|
|
75
|
+
let exitCode = null;
|
|
76
76
|
try {
|
|
77
|
-
// Normalize line endings to LF (\n)
|
|
78
77
|
const normalizedScriptContent = scriptContent.replace(/\r\n/g, '\n');
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
78
|
+
yield (0, promises_1.writeFile)(tempFilePath, normalizedScriptContent, 'latin1');
|
|
79
|
+
process.stderr.write(`Temporary script written to: ${tempFilePath}\n`);
|
|
80
|
+
// --- Force shell execution with explicit quoting for cmd.exe ---
|
|
81
|
+
// Meticulously construct the command string exactly as cmd.exe needs it.
|
|
82
|
+
// Rule 1: The executable path with spaces MUST be quoted.
|
|
83
|
+
// Rule 2: Arguments containing spaces MUST be quoted (or potentially escaped, but quotes are safer).
|
|
84
|
+
// Assuming profile name might have spaces, tempFilePath might too (less likely but possible).
|
|
85
|
+
const commandStringForShell = `"${codesysExePath}" --profile="${codesysProfileName}" --noUI --runscript="${tempFilePath}"`;
|
|
86
|
+
process.stderr.write(`Attempting to execute via shell: ${commandStringForShell}\n`); // Log the EXACT command
|
|
87
|
+
const spawnResult = yield new Promise((resolve) => {
|
|
88
|
+
let stdoutData = '';
|
|
89
|
+
let stderrData = '';
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const timeoutSignal = controller.signal;
|
|
92
|
+
const timeoutDuration = 30000; // 30 seconds
|
|
93
|
+
// Use shell: true. Pass the command string directly.
|
|
94
|
+
// On Windows, this uses cmd.exe.
|
|
95
|
+
// Pass the command string as the first argument when shell is true and args is empty/undefined.
|
|
96
|
+
const childProcess = (0, child_process_1.spawn)(commandStringForShell, [], {
|
|
97
|
+
shell: true, // FORCE shell usage
|
|
98
|
+
windowsHide: false,
|
|
99
|
+
signal: timeoutSignal
|
|
100
|
+
});
|
|
101
|
+
const timeoutId = setTimeout(() => {
|
|
102
|
+
process.stderr.write(`TIMEOUT: CODESYS process timed out after ${timeoutDuration / 1000} seconds\n`);
|
|
103
|
+
controller.abort();
|
|
104
|
+
}, timeoutDuration);
|
|
105
|
+
childProcess.stdout.on('data', (data) => {
|
|
106
|
+
const chunk = data.toString();
|
|
107
|
+
stdoutData += chunk;
|
|
108
|
+
process.stderr.write(`CODESYS stdout chunk: ${chunk.length > 100 ? chunk.substring(0, 100) + '...' : chunk}\n`);
|
|
109
|
+
});
|
|
110
|
+
childProcess.stderr.on('data', (data) => {
|
|
111
|
+
// **** THIS IS CRITICAL - CAPTURE STDERR ****
|
|
112
|
+
const chunk = data.toString();
|
|
113
|
+
stderrData += chunk;
|
|
114
|
+
process.stderr.write(`>>>> CODESYS stderr chunk: ${chunk}\n`); // Make stderr highly visible
|
|
115
|
+
});
|
|
116
|
+
childProcess.on('error', (error) => {
|
|
93
117
|
var _a;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (stderr)
|
|
98
|
-
combinedOutput += stderr;
|
|
99
|
-
// Log results for debugging
|
|
100
|
-
if (stdout)
|
|
101
|
-
process.stderr.write(`CODESYS stdout: ${stdout.length > 100 ? stdout.substring(0, 100) + '...' : stdout}\n`);
|
|
102
|
-
if (stderr)
|
|
103
|
-
process.stderr.write(`CODESYS stderr: ${stderr}\n`);
|
|
104
|
-
if (error) {
|
|
105
|
-
process.stderr.write(`CODESYS exec error: ${error.message}\n`);
|
|
106
|
-
resolve({ code: (_a = error.code) !== null && _a !== void 0 ? _a : 1, output: combinedOutput });
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
resolve({ code: 0, output: combinedOutput });
|
|
110
|
-
}
|
|
118
|
+
clearTimeout(timeoutId);
|
|
119
|
+
process.stderr.write(`CODESYS spawn error (shell mode): ${error.message}\n`);
|
|
120
|
+
resolve({ code: (_a = error.errno) !== null && _a !== void 0 ? _a : 1, output: stdoutData + stderrData + `\nSpawn Error: ${error.message}` });
|
|
111
121
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
process.stderr.write(`
|
|
115
|
-
|
|
116
|
-
resolve({ code:
|
|
122
|
+
childProcess.on('close', (code) => {
|
|
123
|
+
clearTimeout(timeoutId);
|
|
124
|
+
process.stderr.write(`CODESYS process closed with code: ${code}\n`);
|
|
125
|
+
const combinedOutput = stdoutData + stderrData;
|
|
126
|
+
resolve({ code: code, output: combinedOutput });
|
|
117
127
|
});
|
|
128
|
+
timeoutSignal.addEventListener('abort', () => {
|
|
129
|
+
process.stderr.write('Abort signal received, killing process if still running.\n');
|
|
130
|
+
// Kill logic remains the same
|
|
131
|
+
if (!childProcess.killed)
|
|
132
|
+
childProcess.kill('SIGTERM');
|
|
133
|
+
setTimeout(() => { if (!childProcess.killed)
|
|
134
|
+
childProcess.kill('SIGKILL'); }, 2000);
|
|
135
|
+
resolve({ code: null, output: stdoutData + stderrData + "\nTIMEOUT: CODESYS process aborted due to timeout." });
|
|
136
|
+
}, { once: true });
|
|
118
137
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// Determine success based on exit code and output markers
|
|
124
|
-
// Note: CODESYS scripting might return exit code 0 even if the script had logical errors.
|
|
125
|
-
// Relying on specific output markers is more robust.
|
|
138
|
+
output = spawnResult.output;
|
|
139
|
+
exitCode = spawnResult.code;
|
|
140
|
+
// Success determination logic remains the same...
|
|
141
|
+
// [...]
|
|
126
142
|
if (output.includes(SCRIPT_SUCCESS_MARKER)) {
|
|
127
143
|
success = true;
|
|
128
144
|
}
|
|
@@ -130,48 +146,49 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
|
|
|
130
146
|
success = false;
|
|
131
147
|
}
|
|
132
148
|
else {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
if (exitCode === 0) {
|
|
150
|
+
console.warn("Warning: CODESYS script finished with exit code 0 but no SCRIPT_SUCCESS marker found.");
|
|
151
|
+
success = !output.includes(SCRIPT_ERROR_MARKER); // Assume success if exit code 0 and no error marker
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
success = false;
|
|
155
|
+
const exitMsg = exitCode !== null ? `exit code ${exitCode}` : 'signal or unknown reason';
|
|
156
|
+
// Prepend error marker if not already present from script/stderr
|
|
137
157
|
if (!output.includes(SCRIPT_ERROR_MARKER)) {
|
|
138
|
-
output
|
|
158
|
+
output = `SCRIPT_ERROR: Process exited with ${exitMsg}. Check CODESYS stderr output above for details.\n${output}`;
|
|
139
159
|
}
|
|
140
160
|
else {
|
|
141
|
-
|
|
161
|
+
// Potentially add exit code info if not already part of the error marker message
|
|
162
|
+
if (!output.includes(`exited with ${exitMsg}`)) {
|
|
163
|
+
output += `\n(Process exited with ${exitMsg})`;
|
|
164
|
+
}
|
|
142
165
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
console.error("Warning: CODESYS script finished with exit code 0 but no SCRIPT_SUCCESS marker found."); // Log warning to stderr
|
|
147
|
-
// Optionally, you could treat this as success or failure depending on requirements
|
|
148
|
-
// For now, let's assume success if exit code is 0 and no error marker.
|
|
149
|
-
if (!output.includes(SCRIPT_ERROR_MARKER)) {
|
|
150
|
-
success = true;
|
|
166
|
+
// Handle timeout message addition if necessary (should be added by abort handler)
|
|
167
|
+
if (exitCode === null && !output.includes("TIMEOUT:") && !output.includes("Spawn Error:")) {
|
|
168
|
+
output += `\nProcess may have been killed externally or timed out without proper exit code reporting.`;
|
|
151
169
|
}
|
|
152
170
|
}
|
|
153
171
|
}
|
|
154
172
|
}
|
|
155
173
|
catch (error) {
|
|
156
|
-
process.stderr.write(`Error
|
|
157
|
-
output += `\nSCRIPT_ERROR: Failed to
|
|
174
|
+
process.stderr.write(`Error during script execution setup: ${error}\n`); // Catch errors *before* spawn
|
|
175
|
+
output += `\nSCRIPT_ERROR: Failed to set up script execution process. ${error.message}`;
|
|
158
176
|
success = false;
|
|
159
177
|
}
|
|
160
178
|
finally {
|
|
161
|
-
//
|
|
179
|
+
// Cleanup logic remains the same...
|
|
162
180
|
try {
|
|
163
181
|
yield (0, promises_1.unlink)(tempFilePath);
|
|
164
|
-
process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`);
|
|
182
|
+
process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`);
|
|
165
183
|
}
|
|
166
184
|
catch (cleanupError) {
|
|
167
|
-
process.stderr.write(`Failed to delete temporary script file ${tempFilePath}: ${cleanupError}\n`);
|
|
168
|
-
|
|
169
|
-
if (success) { // Only add cleanup error if the main process didn't already fail
|
|
185
|
+
process.stderr.write(`Failed to delete temporary script file ${tempFilePath}: ${cleanupError}\n`);
|
|
186
|
+
if (success) { // Only add warning if main process didn't already fail
|
|
170
187
|
output += `\nWARNING: Failed to delete temporary script file ${tempFilePath}. ${cleanupError.message}`;
|
|
171
188
|
}
|
|
172
189
|
}
|
|
173
190
|
}
|
|
174
|
-
process.stderr.write(`Final script output received:\n---\n${output}\n---\n`);
|
|
175
|
-
return { success, output };
|
|
191
|
+
process.stderr.write(`Final script output received (Success: ${success}):\n---\n${output}\n---\n`);
|
|
192
|
+
return { success, output };
|
|
176
193
|
});
|
|
177
194
|
}
|