@codesys/mcp-toolkit 1.0.7 → 1.0.8
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 +103 -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"); // Use spawn directly
|
|
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
|
+
* Uses spawn for better handling of paths with spaces.
|
|
55
56
|
*
|
|
56
57
|
* @param scriptContent The Python script code to execute.
|
|
57
58
|
* @param codesysExePath The full path to the CODESYS.exe executable.
|
|
@@ -69,60 +70,95 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
|
|
|
69
70
|
}
|
|
70
71
|
// Create a temporary file for the script
|
|
71
72
|
const tempDir = os.tmpdir();
|
|
72
|
-
|
|
73
|
+
// Ensure temp file name is safe for command line (though spawn handles args better)
|
|
74
|
+
const tempFileName = `codesys_script_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
|
|
73
75
|
const tempFilePath = path.join(tempDir, tempFileName);
|
|
74
76
|
let output = '';
|
|
75
77
|
let success = false;
|
|
78
|
+
let exitCode = null;
|
|
76
79
|
try {
|
|
77
80
|
// Normalize line endings to LF (\n)
|
|
78
81
|
const normalizedScriptContent = scriptContent.replace(/\r\n/g, '\n');
|
|
79
82
|
// Write the script content to the temporary file using latin1 encoding
|
|
80
|
-
yield (0, promises_1.writeFile)(tempFilePath, normalizedScriptContent, 'latin1');
|
|
81
|
-
process.stderr.write(`Temporary script written to: ${tempFilePath} with latin1 encoding and LF line endings\n`);
|
|
82
|
-
//
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
83
|
+
yield (0, promises_1.writeFile)(tempFilePath, normalizedScriptContent, 'latin1');
|
|
84
|
+
process.stderr.write(`Temporary script written to: ${tempFilePath} with latin1 encoding and LF line endings\n`);
|
|
85
|
+
// --- Use spawn ---
|
|
86
|
+
const command = codesysExePath; // The executable itself
|
|
87
|
+
const args = [
|
|
88
|
+
`--profile=${codesysProfileName}`, // Format expected by CODESYS.exe
|
|
89
|
+
'--noUI',
|
|
90
|
+
`--runscript=${tempFilePath}` // Format expected by CODESYS.exe
|
|
91
|
+
// If CODESYS requires quotes *around* the profile/script path *value*, adjust like:
|
|
92
|
+
// `--profile="${codesysProfileName}"`,
|
|
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) => {
|
|
99
|
+
let stdoutData = '';
|
|
100
|
+
let stderrData = '';
|
|
101
|
+
// Spawn the process. Detached:false is default. Timeout via AbortController recommended for spawn.
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timeoutSignal = controller.signal;
|
|
104
|
+
const timeoutDuration = 30000; // 30 seconds
|
|
105
|
+
const childProcess = (0, child_process_1.spawn)(command, args, {
|
|
106
|
+
windowsHide: true, // Hides the console window on Windows
|
|
107
|
+
signal: timeoutSignal // Use AbortSignal for timeout
|
|
108
|
+
});
|
|
109
|
+
const timeoutId = setTimeout(() => {
|
|
110
|
+
process.stderr.write(`TIMEOUT: CODESYS process timed out after ${timeoutDuration / 1000} seconds\n`);
|
|
111
|
+
controller.abort(); // Abort the process via the signal
|
|
112
|
+
}, timeoutDuration);
|
|
113
|
+
childProcess.stdout.on('data', (data) => {
|
|
114
|
+
const chunk = data.toString();
|
|
115
|
+
stdoutData += chunk;
|
|
116
|
+
process.stderr.write(`CODESYS stdout chunk: ${chunk.length > 100 ? chunk.substring(0, 100) + '...' : chunk}\n`);
|
|
117
|
+
});
|
|
118
|
+
childProcess.stderr.on('data', (data) => {
|
|
119
|
+
const chunk = data.toString();
|
|
120
|
+
stderrData += chunk;
|
|
121
|
+
process.stderr.write(`CODESYS stderr chunk: ${chunk}\n`); // Log stderr more verbosely
|
|
122
|
+
});
|
|
123
|
+
childProcess.on('error', (error) => {
|
|
93
124
|
var _a;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
}
|
|
125
|
+
clearTimeout(timeoutId); // Clear timeout on explicit error
|
|
126
|
+
// This usually means the process couldn't be spawned (e.g., EACCES, ENOENT)
|
|
127
|
+
process.stderr.write(`CODESYS spawn error: ${error.message}\n`);
|
|
128
|
+
// Don't reject here, resolve with error info like exec did
|
|
129
|
+
resolve({ code: (_a = error.errno) !== null && _a !== void 0 ? _a : 1, output: stdoutData + stderrData + `\nSpawn Error: ${error.message}` });
|
|
111
130
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
process.stderr.write(`
|
|
115
|
-
|
|
116
|
-
resolve({ code:
|
|
131
|
+
childProcess.on('close', (code) => {
|
|
132
|
+
clearTimeout(timeoutId); // Clear timeout on successful close
|
|
133
|
+
process.stderr.write(`CODESYS process closed with code: ${code}\n`);
|
|
134
|
+
const combinedOutput = stdoutData + stderrData;
|
|
135
|
+
resolve({ code: code, output: combinedOutput });
|
|
117
136
|
});
|
|
137
|
+
// Handle the timeout signal specifically
|
|
138
|
+
timeoutSignal.addEventListener('abort', () => {
|
|
139
|
+
process.stderr.write('Abort signal received, killing process if still running.\n');
|
|
140
|
+
if (!childProcess.killed) {
|
|
141
|
+
childProcess.kill('SIGTERM'); // Attempt graceful kill first
|
|
142
|
+
// Optionally force kill after a short delay if SIGTERM doesn't work
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
if (!childProcess.killed) {
|
|
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.
|
|
156
|
+
resolve({ code: null, output: stdoutData + stderrData + "\nTIMEOUT: CODESYS process aborted due to timeout." });
|
|
157
|
+
}, { once: true }); // Ensure listener is called only once
|
|
118
158
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const exitCode = execResult.code;
|
|
122
|
-
process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`); // Use process.stderr
|
|
159
|
+
output = spawnResult.output;
|
|
160
|
+
exitCode = spawnResult.code; // Can be null if process killed before exit code retrieved
|
|
123
161
|
// 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.
|
|
126
162
|
if (output.includes(SCRIPT_SUCCESS_MARKER)) {
|
|
127
163
|
success = true;
|
|
128
164
|
}
|
|
@@ -130,48 +166,50 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
|
|
|
130
166
|
success = false;
|
|
131
167
|
}
|
|
132
168
|
else {
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
// Fallback logic (handle null exit code)
|
|
170
|
+
if (exitCode === 0) {
|
|
171
|
+
console.warn("Warning: CODESYS script finished with exit code 0 but no SCRIPT_SUCCESS marker found.");
|
|
172
|
+
success = !output.includes(SCRIPT_ERROR_MARKER); // Assume success if exit code 0 and no error marker
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
success = false;
|
|
176
|
+
const exitMsg = exitCode !== null ? `exit code ${exitCode}` : 'signal or unknown reason';
|
|
137
177
|
if (!output.includes(SCRIPT_ERROR_MARKER)) {
|
|
138
|
-
output += `\nSCRIPT_ERROR: Process exited with
|
|
178
|
+
output += `\nSCRIPT_ERROR: Process exited with ${exitMsg}. Check CODESYS stderr output above for details.`;
|
|
139
179
|
}
|
|
140
180
|
else {
|
|
141
|
-
output += `\nSCRIPT_ERROR: Process exited with
|
|
181
|
+
output += `\nSCRIPT_ERROR: Process exited with ${exitMsg}.`;
|
|
142
182
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
success = true;
|
|
183
|
+
// Add timeout message specifically if that was the cause
|
|
184
|
+
if (output.includes("TIMEOUT: CODESYS process aborted")) {
|
|
185
|
+
// Message already added by the abort handler
|
|
186
|
+
}
|
|
187
|
+
else if (exitCode === null && !output.includes("Spawn Error:")) {
|
|
188
|
+
// If exit code is null but not due to spawn error or handled timeout, add generic killed message
|
|
189
|
+
output += `\nProcess may have been killed externally or timed out without proper exit code reporting.`;
|
|
151
190
|
}
|
|
152
191
|
}
|
|
153
192
|
}
|
|
154
193
|
}
|
|
155
194
|
catch (error) {
|
|
156
|
-
process.stderr.write(`Error executing CODESYS script: ${error}\n`);
|
|
157
|
-
output += `\nSCRIPT_ERROR: Failed to
|
|
195
|
+
process.stderr.write(`Error executing CODESYS script: ${error}\n`);
|
|
196
|
+
output += `\nSCRIPT_ERROR: Failed to set up script execution process. ${error.message}`;
|
|
158
197
|
success = false;
|
|
159
198
|
}
|
|
160
199
|
finally {
|
|
161
200
|
// Clean up the temporary file
|
|
162
201
|
try {
|
|
163
202
|
yield (0, promises_1.unlink)(tempFilePath);
|
|
164
|
-
process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`);
|
|
203
|
+
process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`);
|
|
165
204
|
}
|
|
166
205
|
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
|
|
206
|
+
process.stderr.write(`Failed to delete temporary script file ${tempFilePath}: ${cleanupError}\n`);
|
|
207
|
+
if (success) {
|
|
170
208
|
output += `\nWARNING: Failed to delete temporary script file ${tempFilePath}. ${cleanupError.message}`;
|
|
171
209
|
}
|
|
172
210
|
}
|
|
173
211
|
}
|
|
174
|
-
process.stderr.write(`Final script output received:\n---\n${output}\n---\n`);
|
|
175
|
-
return { success, output };
|
|
212
|
+
process.stderr.write(`Final script output received (Success: ${success}):\n---\n${output}\n---\n`);
|
|
213
|
+
return { success, output };
|
|
176
214
|
});
|
|
177
215
|
}
|