@codesys/mcp-toolkit 1.0.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/LICENSE +21 -0
- package/README.md +123 -0
- package/dist/bin.js +30 -0
- package/dist/codesys_interop.js +180 -0
- package/dist/server - Copy.js +1444 -0
- package/dist/server.js +1444 -0
- package/package.json +47 -0
- package/scriptDoc.txt +8679 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 CODESYS MCP Server Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# @codesys/mcp-toolkit
|
|
2
|
+
|
|
3
|
+
This project provides a Model Context Protocol (MCP) server that allows interaction with a local CODESYS V3 development environment (tested with 3.5 SP21) via its scripting interface. It enables MCP clients like Claude Desktop to perform actions such as opening/creating/saving projects, creating POUs, adding code, and compiling projects.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
Currently implemented features:
|
|
8
|
+
|
|
9
|
+
* **Resources:**
|
|
10
|
+
* `codesys://project/status`: Checks scripting status and if a project is open.
|
|
11
|
+
* `codesys://project/structure`: Retrieves the structure of the open project as text.
|
|
12
|
+
* `codesys://pou/{+pou_path}/code`: Reads the implementation or declaration code of a specific POU or DUT/GVL.
|
|
13
|
+
* **Tools:**
|
|
14
|
+
* `open_project`: Opens a specified CODESYS project file (`.project`).
|
|
15
|
+
* `create_project`: Creates a new CODESYS project file (`.project`), optionally from a template.
|
|
16
|
+
* `save_project`: Saves the currently open CODESYS project.
|
|
17
|
+
* `create_pou`: Creates a new POU (Program, Function Block, Function) with a specified language under a parent object in the project tree.
|
|
18
|
+
* `add_code`: Sets the implementation code for a specified POU.
|
|
19
|
+
* `compile_project`: Initiates the compilation of the open project.
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
1. **CODESYS V3 Installation:** A working CODESYS V3 installation (e.g., 3.5 SP21) with the **Scripting Engine** component enabled.
|
|
24
|
+
2. **Node.js:** Node.js (version 18 or later recommended) installed on your system. You can download it from [nodejs.org](https://nodejs.org/).
|
|
25
|
+
3. **MCP Client:** An MCP client application, such as **Claude Desktop**.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Option 1: Install from npm (Recommended)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g @codesys/mcp-toolkit
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This will install the server globally, making the `codesys-mcp-toolkit` command available in your terminal.
|
|
36
|
+
|
|
37
|
+
### Option 2: Install from Source
|
|
38
|
+
|
|
39
|
+
1. **Clone Repository:**
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/yourusername/codesys-mcp-server.git
|
|
42
|
+
cd codesys-mcp-server
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. **Install Dependencies:**
|
|
46
|
+
```bash
|
|
47
|
+
npm install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. **Build the Project:**
|
|
51
|
+
```bash
|
|
52
|
+
npm run build
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
4. **Install Globally:**
|
|
56
|
+
```bash
|
|
57
|
+
npm install -g .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Running the Server (via Claude Desktop)
|
|
61
|
+
|
|
62
|
+
This server is designed to be launched automatically by an MCP client like Claude Desktop using its stdio connection mechanism. You don't typically run it directly from the command line yourself.
|
|
63
|
+
|
|
64
|
+
## Configuring Claude Desktop
|
|
65
|
+
|
|
66
|
+
1. **Locate Configuration File:** Find Claude Desktop's MCP configuration file. On Windows, it's typically located at:
|
|
67
|
+
`C:\Users\<YourUsername>\AppData\Roaming\Claude\claude_desktop_config.json`
|
|
68
|
+
(Replace `<YourUsername>` with your actual Windows username).
|
|
69
|
+
2. **Edit Configuration:** Open `claude_desktop_config.json` in a text editor.
|
|
70
|
+
3. **Add Server Entry:** Add an entry for the CODESYS server within the `mcpServers` object. Make sure to replace the example paths and profile name with your actual values.
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
// ... other servers like puppeteer, filesystem ...
|
|
76
|
+
|
|
77
|
+
"codesys_local": {
|
|
78
|
+
"command": "npx",
|
|
79
|
+
"args": [
|
|
80
|
+
"-y",
|
|
81
|
+
"@codesys/mcp-toolkit",
|
|
82
|
+
"--codesys-path", "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe",
|
|
83
|
+
"--codesys-profile", "CODESYS V3.5 SP21"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Important:**
|
|
91
|
+
* Replace `C:/Users/ek6430/Desktop/codesys-mcp-server/dist/server.js` with the correct **absolute path** to the compiled `server.js` file on your system. Use forward slashes (`/`) for path separators within this JSON string.
|
|
92
|
+
* Replace `"C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe"` with the correct **absolute path** to your `CODESYS.exe`. Note the use of double backslashes (`\\`) because backslashes need to be escaped within JSON strings.
|
|
93
|
+
* Replace `"CODESYS V3.5 SP21"` with the **exact name** of your CODESYS profile (the one with scripting enabled). Find this name when starting CODESYS or in its title bar.
|
|
94
|
+
4. **Save the File.**
|
|
95
|
+
5. **Restart Claude Desktop:** Close Claude Desktop completely and restart it. It will now read the updated configuration and attempt to launch the CODESYS server with the specified arguments.
|
|
96
|
+
6. **Verify Connection:** Claude Desktop should indicate that it has successfully connected to the "codesys_local" server. You might see the server listed in your available MCP integrations.
|
|
97
|
+
7. **Use Tools/Resources:** You can now interact with the server from within Claude. For example:
|
|
98
|
+
* Ask Claude to "Use the `project-status` resource from the CODESYS Local server."
|
|
99
|
+
* Ask Claude to "Use the `open_project` tool from CODESYS Local with filePath set to 'C:/Path/To/Your/Project.project'." (Replace with a real project path).
|
|
100
|
+
|
|
101
|
+
## Troubleshooting
|
|
102
|
+
|
|
103
|
+
* **Configuration Errors:** If Claude Desktop fails to connect, check its logs (often found in `C:\Users\<YourUsername>\AppData\Roaming\Claude\logs\`). Look for errors related to the `codesys_local` server.
|
|
104
|
+
* Verify the paths and profile name in `claude_desktop_config.json` are absolutely correct. Pay attention to path separators (`/` for the script path, `\\` for the CODESYS path within the JSON string).
|
|
105
|
+
* Ensure the profile name exactly matches the one in CODESYS.
|
|
106
|
+
* **Server Script Errors:** If the server starts but tools/resources fail, check the server's output in the Claude Desktop logs. The `executeCodesysScript` function in the server code includes `console.log` and `console.error` statements that might appear in the logs, providing clues about script execution issues.
|
|
107
|
+
* **Permissions:** The user account running Claude Desktop (and thus the Node.js server) needs permission to execute `CODESYS.exe` and write temporary files to the OS's temporary directory.
|
|
108
|
+
* **CODESYS Script Errors:** If a CODESYS script itself fails (e.g., invalid POU path, project not open), the error message from the script should be returned in the MCP tool/resource response. Check the `output` field in the response.
|
|
109
|
+
* **CODESYS Not Running/Responding:** The `executeCodesysScript` might time out or fail if the CODESYS scripting engine cannot be launched properly. Ensure CODESYS isn't already running in a conflicting way (though `--noUI` should help).
|
|
110
|
+
|
|
111
|
+
## Future Enhancements (Based on Provided API Details)
|
|
112
|
+
|
|
113
|
+
The detailed API information provided (especially `ScriptOnline`) opens up possibilities for significant future enhancements, such as:
|
|
114
|
+
|
|
115
|
+
* Connecting to a running PLC (`create_online_device`, `connect`).
|
|
116
|
+
* Logging into the PLC (`login`).
|
|
117
|
+
* Starting/Stopping applications (`start`, `stop`).
|
|
118
|
+
* Reading/Writing live variable values (`read_value`, `set_prepared_value`, `write_prepared_values`).
|
|
119
|
+
* Forcing/Unforcing values (`force_prepared_values`, `unforce_all_values`).
|
|
120
|
+
* Performing downloads and online changes (`login` with options).
|
|
121
|
+
* Managing PLC files (`download_file`, `upload_file`, `delete_file`).
|
|
122
|
+
* Handling PLC user management (`create_live_user_management`).
|
|
123
|
+
* License management (`activate_license`).
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* CODESYS MCP Server CLI
|
|
5
|
+
* Command-line interface for starting the CODESYS MCP server
|
|
6
|
+
* with configurable CODESYS executable paths and profile names.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
const commander_1 = require("commander");
|
|
10
|
+
// Define version from package.json
|
|
11
|
+
const packageJson = require('../package.json');
|
|
12
|
+
const version = packageJson.version;
|
|
13
|
+
commander_1.program
|
|
14
|
+
.name('codesys-mcp-toolkit')
|
|
15
|
+
.description('Model Context Protocol (MCP) server for CODESYS automation platform')
|
|
16
|
+
.version(version)
|
|
17
|
+
.option('-e, --codesys-path <path>', 'Path to CODESYS executable', 'C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe')
|
|
18
|
+
.option('-p, --codesys-profile <profile>', 'CODESYS profile name', 'CODESYS V3.5 SP21')
|
|
19
|
+
.parse(process.argv);
|
|
20
|
+
const options = commander_1.program.opts();
|
|
21
|
+
// Log startup message for debugging
|
|
22
|
+
console.error(`Starting CODESYS MCP Server v${version}`);
|
|
23
|
+
console.error(`Using CODESYS path: ${options.codesysPath}`);
|
|
24
|
+
console.error(`Using CODESYS profile: ${options.codesysProfile}`);
|
|
25
|
+
// Pass command line arguments to the CODESYS executable path and profile
|
|
26
|
+
// The server.js file expects these as positional parameters
|
|
27
|
+
process.argv[2] = options.codesysPath;
|
|
28
|
+
process.argv[3] = options.codesysProfile;
|
|
29
|
+
// Import and start the server
|
|
30
|
+
import('./server.js');
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.executeCodesysScript = executeCodesysScript;
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
const promises_1 = require("fs/promises");
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
// Define expected success/error markers from the Python scripts
|
|
51
|
+
const SCRIPT_SUCCESS_MARKER = 'SCRIPT_SUCCESS';
|
|
52
|
+
const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
|
|
53
|
+
/**
|
|
54
|
+
* Executes a CODESYS Python script using the command line interface.
|
|
55
|
+
*
|
|
56
|
+
* @param scriptContent The Python script code to execute.
|
|
57
|
+
* @param codesysExePath The full path to the CODESYS.exe executable.
|
|
58
|
+
* @param codesysProfileName The name of the CODESYS profile to use for scripting.
|
|
59
|
+
* @returns A promise resolving to an object containing the success status and the script's output.
|
|
60
|
+
*/
|
|
61
|
+
function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
// Validate arguments passed to the function
|
|
64
|
+
if (!codesysExePath) {
|
|
65
|
+
throw new Error('CODESYS executable path was not provided to executeCodesysScript.');
|
|
66
|
+
}
|
|
67
|
+
if (!codesysProfileName) {
|
|
68
|
+
throw new Error('CODESYS profile name was not provided to executeCodesysScript.');
|
|
69
|
+
}
|
|
70
|
+
// Create a temporary file for the script
|
|
71
|
+
const tempDir = os.tmpdir();
|
|
72
|
+
const tempFileName = `codesys_script_${Date.now()}_${Math.random().toString(36).substring(7)}.py`;
|
|
73
|
+
const tempFilePath = path.join(tempDir, tempFileName);
|
|
74
|
+
let output = '';
|
|
75
|
+
let success = false;
|
|
76
|
+
try {
|
|
77
|
+
// Normalize line endings to LF (\n)
|
|
78
|
+
const normalizedScriptContent = scriptContent.replace(/\r\n/g, '\n');
|
|
79
|
+
// Write the script content to the temporary file using latin1 encoding
|
|
80
|
+
yield (0, promises_1.writeFile)(tempFilePath, normalizedScriptContent, 'latin1'); // Explicitly use latin1
|
|
81
|
+
process.stderr.write(`Temporary script written to: ${tempFilePath} with latin1 encoding and LF line endings\n`); // Use process.stderr
|
|
82
|
+
// Add --additionalfolder argument
|
|
83
|
+
const additionalFolderPath = "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\AdditionalFolders\\Default"; // Assuming Default instance
|
|
84
|
+
// Fix: Use shell:true and pass the entire command as a single string without additionalfolder
|
|
85
|
+
const commandWithArgs = `"${codesysExePath}" --profile="${codesysProfileName}" --noUI --runscript="${tempFilePath}"`;
|
|
86
|
+
process.stderr.write(`Executing: ${commandWithArgs}\n`); // Log the command string
|
|
87
|
+
// Spawn the CODESYS process using shell:true for proper argument handling
|
|
88
|
+
const codesysProcess = (0, child_process_1.spawn)(commandWithArgs, {
|
|
89
|
+
stdio: ['ignore', 'pipe', 'pipe'], // Ignore stdin, pipe stdout and stderr
|
|
90
|
+
shell: true // Use shell to handle the command string properly
|
|
91
|
+
});
|
|
92
|
+
// Capture stdout
|
|
93
|
+
codesysProcess.stdout.on('data', (data) => {
|
|
94
|
+
const chunk = data.toString();
|
|
95
|
+
// console.error('CODESYS stdout:', chunk); // Optional: Original debug log
|
|
96
|
+
output += chunk;
|
|
97
|
+
});
|
|
98
|
+
// Capture stderr
|
|
99
|
+
codesysProcess.stderr.on('data', (data) => {
|
|
100
|
+
const chunk = data.toString();
|
|
101
|
+
process.stderr.write(`CODESYS stderr: ${chunk}\n`); // Use process.stderr
|
|
102
|
+
output += chunk; // Include stderr in the output for debugging
|
|
103
|
+
});
|
|
104
|
+
// Wait for the process to exit with a timeout
|
|
105
|
+
const exitCode = yield new Promise((resolve, reject) => {
|
|
106
|
+
// Add timeout handling
|
|
107
|
+
const timeoutMs = 30000; // 30 seconds
|
|
108
|
+
const timeout = setTimeout(() => {
|
|
109
|
+
process.stderr.write(`TIMEOUT: CODESYS process did not complete in ${timeoutMs}ms\n`);
|
|
110
|
+
// Force resolve with 0 to proceed with parsing output collected so far
|
|
111
|
+
codesysProcess.kill();
|
|
112
|
+
resolve(0);
|
|
113
|
+
}, timeoutMs);
|
|
114
|
+
codesysProcess.on('close', (code) => {
|
|
115
|
+
clearTimeout(timeout);
|
|
116
|
+
resolve(code !== null && code !== void 0 ? code : 0);
|
|
117
|
+
});
|
|
118
|
+
codesysProcess.on('error', (err) => {
|
|
119
|
+
clearTimeout(timeout);
|
|
120
|
+
// Error starting the process itself (e.g., command not found)
|
|
121
|
+
process.stderr.write(`Failed to start CODESYS process: ${err.message}\n`); // Use process.stderr
|
|
122
|
+
reject(err);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`); // Use process.stderr
|
|
126
|
+
// Determine success based on exit code and output markers
|
|
127
|
+
// Note: CODESYS scripting might return exit code 0 even if the script had logical errors.
|
|
128
|
+
// Relying on specific output markers is more robust.
|
|
129
|
+
if (output.includes(SCRIPT_SUCCESS_MARKER)) {
|
|
130
|
+
success = true;
|
|
131
|
+
}
|
|
132
|
+
else if (output.includes(SCRIPT_ERROR_MARKER)) {
|
|
133
|
+
success = false;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// If no markers found, fall back to exit code (less reliable)
|
|
137
|
+
success = exitCode === 0;
|
|
138
|
+
if (!success) {
|
|
139
|
+
// Append the specific CODESYS error if available and no other marker found
|
|
140
|
+
if (!output.includes(SCRIPT_ERROR_MARKER)) {
|
|
141
|
+
output += `\nSCRIPT_ERROR: Process exited with non-zero code ${exitCode}. Check CODESYS stderr output above for details.`;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
output += `\nSCRIPT_ERROR: Process exited with non-zero code ${exitCode}.`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// If exit code is 0 but no success marker, treat as potential issue or simple script
|
|
149
|
+
console.error("Warning: CODESYS script finished with exit code 0 but no SCRIPT_SUCCESS marker found."); // Log warning to stderr
|
|
150
|
+
// Optionally, you could treat this as success or failure depending on requirements
|
|
151
|
+
// For now, let's assume success if exit code is 0 and no error marker.
|
|
152
|
+
if (!output.includes(SCRIPT_ERROR_MARKER)) {
|
|
153
|
+
success = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
process.stderr.write(`Error executing CODESYS script: ${error}\n`); // Use process.stderr
|
|
160
|
+
output += `\nSCRIPT_ERROR: Failed to execute script process. ${error.message}`;
|
|
161
|
+
success = false;
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
// Clean up the temporary file
|
|
165
|
+
try {
|
|
166
|
+
yield (0, promises_1.unlink)(tempFilePath);
|
|
167
|
+
process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`); // Use process.stderr
|
|
168
|
+
}
|
|
169
|
+
catch (cleanupError) {
|
|
170
|
+
process.stderr.write(`Failed to delete temporary script file ${tempFilePath}: ${cleanupError}\n`); // Use process.stderr
|
|
171
|
+
// Don't overwrite the original error if cleanup fails
|
|
172
|
+
if (success) { // Only add cleanup error if the main process didn't already fail
|
|
173
|
+
output += `\nWARNING: Failed to delete temporary script file ${tempFilePath}. ${cleanupError.message}`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
process.stderr.write(`Final script output received:\n---\n${output}\n---\n`); // Use process.stderr
|
|
178
|
+
return { success, output }; // Revert return object
|
|
179
|
+
});
|
|
180
|
+
}
|