@codesys/mcp-toolkit 1.0.8 → 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 +44 -65
- 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,7 +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
|
-
*
|
|
55
|
+
* Tries using spawn with shell:true and explicit quoting for cmd.exe.
|
|
56
56
|
*
|
|
57
57
|
* @param scriptContent The Python script code to execute.
|
|
58
58
|
* @param codesysExePath The full path to the CODESYS.exe executable.
|
|
@@ -61,54 +61,46 @@ const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
|
|
|
61
61
|
*/
|
|
62
62
|
function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName) {
|
|
63
63
|
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
-
// Validate arguments passed to the function
|
|
65
64
|
if (!codesysExePath) {
|
|
66
65
|
throw new Error('CODESYS executable path was not provided to executeCodesysScript.');
|
|
67
66
|
}
|
|
68
67
|
if (!codesysProfileName) {
|
|
69
68
|
throw new Error('CODESYS profile name was not provided to executeCodesysScript.');
|
|
70
69
|
}
|
|
71
|
-
// Create a temporary file for the script
|
|
72
70
|
const tempDir = os.tmpdir();
|
|
73
|
-
// Ensure temp file name is safe for command line (though spawn handles args better)
|
|
74
71
|
const tempFileName = `codesys_script_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
|
|
75
72
|
const tempFilePath = path.join(tempDir, tempFileName);
|
|
76
73
|
let output = '';
|
|
77
74
|
let success = false;
|
|
78
75
|
let exitCode = null;
|
|
79
76
|
try {
|
|
80
|
-
// Normalize line endings to LF (\n)
|
|
81
77
|
const normalizedScriptContent = scriptContent.replace(/\r\n/g, '\n');
|
|
82
|
-
// Write the script content to the temporary file using latin1 encoding
|
|
83
78
|
yield (0, promises_1.writeFile)(tempFilePath, normalizedScriptContent, 'latin1');
|
|
84
|
-
process.stderr.write(`Temporary script written to: ${tempFilePath}
|
|
85
|
-
// ---
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// `--runscript="${tempFilePath}"`,
|
|
94
|
-
// But usually, spawn handles the argument separation correctly without needing inner quotes unless the *program itself* requires them.
|
|
95
|
-
// Test without inner quotes first.
|
|
96
|
-
];
|
|
97
|
-
process.stderr.write(`Spawning: ${command} with args: ${JSON.stringify(args)}\n`);
|
|
98
|
-
const spawnResult = yield new Promise((resolve, reject) => {
|
|
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) => {
|
|
99
88
|
let stdoutData = '';
|
|
100
89
|
let stderrData = '';
|
|
101
|
-
// Spawn the process. Detached:false is default. Timeout via AbortController recommended for spawn.
|
|
102
90
|
const controller = new AbortController();
|
|
103
91
|
const timeoutSignal = controller.signal;
|
|
104
92
|
const timeoutDuration = 30000; // 30 seconds
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
108
100
|
});
|
|
109
101
|
const timeoutId = setTimeout(() => {
|
|
110
102
|
process.stderr.write(`TIMEOUT: CODESYS process timed out after ${timeoutDuration / 1000} seconds\n`);
|
|
111
|
-
controller.abort();
|
|
103
|
+
controller.abort();
|
|
112
104
|
}, timeoutDuration);
|
|
113
105
|
childProcess.stdout.on('data', (data) => {
|
|
114
106
|
const chunk = data.toString();
|
|
@@ -116,49 +108,37 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
|
|
|
116
108
|
process.stderr.write(`CODESYS stdout chunk: ${chunk.length > 100 ? chunk.substring(0, 100) + '...' : chunk}\n`);
|
|
117
109
|
});
|
|
118
110
|
childProcess.stderr.on('data', (data) => {
|
|
111
|
+
// **** THIS IS CRITICAL - CAPTURE STDERR ****
|
|
119
112
|
const chunk = data.toString();
|
|
120
113
|
stderrData += chunk;
|
|
121
|
-
process.stderr.write(
|
|
114
|
+
process.stderr.write(`>>>> CODESYS stderr chunk: ${chunk}\n`); // Make stderr highly visible
|
|
122
115
|
});
|
|
123
116
|
childProcess.on('error', (error) => {
|
|
124
117
|
var _a;
|
|
125
|
-
clearTimeout(timeoutId);
|
|
126
|
-
|
|
127
|
-
process.stderr.write(`CODESYS spawn error: ${error.message}\n`);
|
|
128
|
-
// Don't reject here, resolve with error info like exec did
|
|
118
|
+
clearTimeout(timeoutId);
|
|
119
|
+
process.stderr.write(`CODESYS spawn error (shell mode): ${error.message}\n`);
|
|
129
120
|
resolve({ code: (_a = error.errno) !== null && _a !== void 0 ? _a : 1, output: stdoutData + stderrData + `\nSpawn Error: ${error.message}` });
|
|
130
121
|
});
|
|
131
122
|
childProcess.on('close', (code) => {
|
|
132
|
-
clearTimeout(timeoutId);
|
|
123
|
+
clearTimeout(timeoutId);
|
|
133
124
|
process.stderr.write(`CODESYS process closed with code: ${code}\n`);
|
|
134
125
|
const combinedOutput = stdoutData + stderrData;
|
|
135
126
|
resolve({ code: code, output: combinedOutput });
|
|
136
127
|
});
|
|
137
|
-
// Handle the timeout signal specifically
|
|
138
128
|
timeoutSignal.addEventListener('abort', () => {
|
|
139
129
|
process.stderr.write('Abort signal received, killing process if still running.\n');
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
process.stderr.write('SIGTERM ineffective, sending SIGKILL.\n');
|
|
146
|
-
childProcess.kill('SIGKILL');
|
|
147
|
-
}
|
|
148
|
-
}, 2000); // 2 second grace period
|
|
149
|
-
}
|
|
150
|
-
// Resolve with timeout information. The 'close' event might still fire later with a specific code.
|
|
151
|
-
// We already logged the timeout, let the 'close' handler report the final code.
|
|
152
|
-
// If 'close' doesn't fire after kill, it might indicate a deeper issue.
|
|
153
|
-
// We could resolve here with a timeout indicator, but letting 'close' handle it is cleaner if it works.
|
|
154
|
-
// If 'close' never fires after abort/kill, the promise might hang. Let's add a failsafe resolve in the listener.
|
|
155
|
-
// Revised: Resolve directly on abort to ensure promise completion.
|
|
130
|
+
// Kill logic remains the same
|
|
131
|
+
if (!childProcess.killed)
|
|
132
|
+
childProcess.kill('SIGTERM');
|
|
133
|
+
setTimeout(() => { if (!childProcess.killed)
|
|
134
|
+
childProcess.kill('SIGKILL'); }, 2000);
|
|
156
135
|
resolve({ code: null, output: stdoutData + stderrData + "\nTIMEOUT: CODESYS process aborted due to timeout." });
|
|
157
|
-
}, { once: true });
|
|
136
|
+
}, { once: true });
|
|
158
137
|
});
|
|
159
138
|
output = spawnResult.output;
|
|
160
|
-
exitCode = spawnResult.code;
|
|
161
|
-
//
|
|
139
|
+
exitCode = spawnResult.code;
|
|
140
|
+
// Success determination logic remains the same...
|
|
141
|
+
// [...]
|
|
162
142
|
if (output.includes(SCRIPT_SUCCESS_MARKER)) {
|
|
163
143
|
success = true;
|
|
164
144
|
}
|
|
@@ -166,7 +146,6 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
|
|
|
166
146
|
success = false;
|
|
167
147
|
}
|
|
168
148
|
else {
|
|
169
|
-
// Fallback logic (handle null exit code)
|
|
170
149
|
if (exitCode === 0) {
|
|
171
150
|
console.warn("Warning: CODESYS script finished with exit code 0 but no SCRIPT_SUCCESS marker found.");
|
|
172
151
|
success = !output.includes(SCRIPT_ERROR_MARKER); // Assume success if exit code 0 and no error marker
|
|
@@ -174,37 +153,37 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
|
|
|
174
153
|
else {
|
|
175
154
|
success = false;
|
|
176
155
|
const exitMsg = exitCode !== null ? `exit code ${exitCode}` : 'signal or unknown reason';
|
|
156
|
+
// Prepend error marker if not already present from script/stderr
|
|
177
157
|
if (!output.includes(SCRIPT_ERROR_MARKER)) {
|
|
178
|
-
output
|
|
158
|
+
output = `SCRIPT_ERROR: Process exited with ${exitMsg}. Check CODESYS stderr output above for details.\n${output}`;
|
|
179
159
|
}
|
|
180
160
|
else {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// Message already added by the abort handler
|
|
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
|
+
}
|
|
186
165
|
}
|
|
187
|
-
|
|
188
|
-
|
|
166
|
+
// Handle timeout message addition if necessary (should be added by abort handler)
|
|
167
|
+
if (exitCode === null && !output.includes("TIMEOUT:") && !output.includes("Spawn Error:")) {
|
|
189
168
|
output += `\nProcess may have been killed externally or timed out without proper exit code reporting.`;
|
|
190
169
|
}
|
|
191
170
|
}
|
|
192
171
|
}
|
|
193
172
|
}
|
|
194
173
|
catch (error) {
|
|
195
|
-
process.stderr.write(`Error
|
|
174
|
+
process.stderr.write(`Error during script execution setup: ${error}\n`); // Catch errors *before* spawn
|
|
196
175
|
output += `\nSCRIPT_ERROR: Failed to set up script execution process. ${error.message}`;
|
|
197
176
|
success = false;
|
|
198
177
|
}
|
|
199
178
|
finally {
|
|
200
|
-
//
|
|
179
|
+
// Cleanup logic remains the same...
|
|
201
180
|
try {
|
|
202
181
|
yield (0, promises_1.unlink)(tempFilePath);
|
|
203
182
|
process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`);
|
|
204
183
|
}
|
|
205
184
|
catch (cleanupError) {
|
|
206
185
|
process.stderr.write(`Failed to delete temporary script file ${tempFilePath}: ${cleanupError}\n`);
|
|
207
|
-
if (success) {
|
|
186
|
+
if (success) { // Only add warning if main process didn't already fail
|
|
208
187
|
output += `\nWARNING: Failed to delete temporary script file ${tempFilePath}. ${cleanupError.message}`;
|
|
209
188
|
}
|
|
210
189
|
}
|