codesys-build
v0.2.0
Published
Build CODESYS applications from domain-organized Structured Text sources.
Readme
codesys-build
Build CODESYS applications from Structured Text files organized by domain. Author complete ST objects or explicit declaration/implementation pairs, select an entry program in TypeScript, and compile against an existing CODESYS project template.
This package uses the CODESYS scripting interface. It does not use the paid File-Based Storage add-on. By default the IDE build runs in Docker, containing Bun, Wine 11.19 and CODESYS 3.5.22.30. The npm CLI requires Node and Docker on the host. Target device packages, libraries and licenses must still match the project template. Local Windows/Wine installations remain available through explicit codesys settings.
The independent native path targets CODESYS Virtual Control Linux ARM64 4.22. It translates ST with an independently implemented frontend, Clang and LLD; Wine, the IDE, vendor compiler assemblies and a prebuilt application are not required. The actual 42-object frame-assembly app has passed OPC UA-backed end-to-end simulation and all 28 PLC integration tests with this path. See the native compiler guide for setup and limits.
The legacy template/IDE workflow below remains available for other targets.
Install
Install with npm using Node 22.18+ on the 22.x line, or Node 24.3+:
npm install --save-dev codesys-build
npx codesys-build configureconfigure prepares the builder image once using the Docker recipe included in the npm package. It downloads, verifies and installs CODESYS 64 3.5.22.30 and its prerequisites automatically, assuming acceptance of the vendor license and readme. No EXE, account credentials, Bun or Wine installation is needed on the host. Image preparation needs internet access and several gigabytes of space; subsequent builds run without network access. The image matches the Docker server's architecture and includes FEX to run Intel Wine on ARM64. See Docker builds.
The command works before creating a project config. When a config exists, it uses that config's docker.image; --config path/to/config.ts selects another config. Docker caches completed installation steps for repeated setup. Create the project config below, then run:
npx codesys-build check
npx codesys-build buildFirst project
Create codesys-build.config.ts:
import { defineConfig } from "codesys-build";
export const config = defineConfig({
template: "templates/base.project",
entry: "src/Main.st",
task: "MainTask",
});The template must have an active Application and an existing task named MainTask. Its device, library references, cycle interval, priority and watchdog remain configured in CODESYS. During the build, that task's program-call list is replaced with the selected entry. Other tasks retain their configuration and calls. An optional complete tasks plan instead sets cyclic timing, priorities and ordered PROGRAM calls, and removes unspecified tasks from the private build copy; see configuration.
Create src/Main.st:
PROGRAM Main
VAR
Cycles : UDINT;
END_VAR
Cycles := Cycles + 1;
END_PROGRAMAdd package scripts:
{
"scripts": {
"configure:plc": "codesys-build configure",
"check:plc": "codesys-build check",
"build:plc": "codesys-build build",
"open:plc": "codesys-build open",
"stop:plc": "codesys-build stop-worker"
}
}Run them with npm run check:plc and npm run build:plc. check validates source envelopes, file layout, names and entry selection. It does not typecheck ST or prove that CODESYS can compile it. build runs the actual compiler and verifies its diagnostics and artifacts. open and worker commands require a local codesys configuration; Docker builds stop their worker and remove the container on exit.
For a local Wine installation:
import { homedir } from "node:os";
import { join } from "node:path";
import { defineConfig } from "codesys-build";
const prefix = join(homedir(), ".wine.codesys");
export const config = defineConfig({
codesys: {
executable: join(prefix, "drive_c/CODESYS-3.5.22.30/CODESYS/Common/CODESYS.exe"),
profile: "CODESYS V3.5 SP22 Patch 3",
wine: { prefix, binary: "wine", pathBinary: "winepath" },
},
template: "templates/base.project",
entry: "src/Main.st",
});These are configuration values, not environment-variable names. Change installation paths and profile to match your machine. No .env file is required. OS process settings such as PATH and DISPLAY are inherited so the installed tools can run.
Source layout
src/
Main.st
transport/
FB_Conveyor/
source.st
methods/
SetEnabled.st
properties/
IsRunning/
declaration.st
get.st
actions/
ResetCounter.st
ST_ConveyorState.st
framing/
PRG_Framing/
declaration.st
implementation.stCombined executable objects include their PROGRAM / FUNCTION_BLOCK / FUNCTION / METHOD header and matching END_*. A folder's combined source may be source.st or <object-name>.st. A standalone <object-name>.st is also supported.
All these split layouts are accepted:
FB_Motor/declaration.st + FB_Motor/implementation.st
FB_Motor/FB_Motor.declaration.st + FB_Motor/FB_Motor.implementation.st
FB_Motor/declaration.st + FB_Motor/FB_Motor.implementation.st
FB_Motor/FB_Motor.declaration.st + FB_Motor/implementation.st
any-domain/FB_Motor.declaration.st + any-domain/FB_Motor.implementation.stThere is no precedence rule for duplicate sources: two implementations for one object, or a combined file alongside a split pair, are errors. An empty implementation file is valid. A missing implementation file is not.
The source declaration determines the kind and IEC name. Recognized prefixes (FB_, PRG_, F_/FUN_, I_, ST_/E_/T_/U_, GVL_) are checked for consistency when present. Plain type filenames and existing globals such as LineIO.st remain valid. GVLs have no named header, so their filename determines their CODESYS name. Folder names organize the IDE; they do not create IEC namespaces. Application object names must remain unique, ignoring case.
See source conventions for methods, properties, actions, interfaces and parsing limits.
Commands and artifacts
codesys-build check
codesys-build build
codesys-build run
codesys-build open
codesys-build open templates/local.project
codesys-build launch-worker
codesys-build stop-worker
codesys-build build --config path/to/codesys-build.config.tsThe default config path is relative to the working directory. Paths inside a config are relative to that config's directory. The optional project argument to open is relative to the working directory.
Each build replaces the project, boot application and receipt at the same paths. Docker diagnostics are under .codesys/docker/job/.codesys/; a local IDE uses the layout below:
build/
Application.project # Open with codesys-build open
runtime/Application.app # Boot application
runtime/Application.crc # Matching checksum
build.json # Completion receipt, published last
.codesys/
build/ # Latest request, template snapshot and diagnostics
worker/ # Compiler scripts, log and private working project
editor/ # Latest interactive IDE launcher and log
session.jsonoutDir controls this directory. Builds reuse these locations instead of retaining run-* or session directories. A failed compilation invalidates build.json, so run cannot silently deploy an earlier build. The previous published project remains available for inspection. build does not change the runtime.
To run on CODESYS Virtual Control for Linux in Docker, configure the Compose service:
runtime: { composeFile: "compose.yaml", service: "plc" }Then run codesys-build build followed by codesys-build run. The latter uses the completed boot files without launching the IDE: it stops only the selected service, installs the matching .app and .crc, starts the service, and waits for the application's start event in the current runtime log. It does not start a UI, OPC UA bridge, or engineering gateway. See running in Docker and the included Compose example.
With local codesys settings, one background CODESYS worker is retained per output directory. It reuses the loaded project for text-only changes and reloads the template when object membership, folders, entry selection, an explicit task plan or template contents change. Source-owned objects replace their template copies, including child objects removed from source. Failed projects are discarded from the worker's cache. Docker builds start and stop a worker inside each temporary container.
See configuration, operation and troubleshooting, and the frame-assembly example.
Package development
bun install --frozen-lockfile
bun test ./src ./tests
bun run test:python
bun run tsc
bun run checkUnit tests cover parsing, discovery and configuration. CLI tests use a fake CODESYS executable to exercise real subprocesses, locking, worker reuse, failures and artifacts. Python tests replace only the CODESYS API. Those tests do not substitute for the real compiler verification recorded with the example.
The parser audit records checks against 5,068 real source objects, seeded adversarial tests, compiler probes and known boundary ambiguities. Normal CODESYS application compilation covers reachable POUs; unused implementations can still contain compiler errors.
Independent native builds
Use defineNativeConfig and build-native (or buildNativeProject) to compile
without vendor compiler tools. check and run also accept native configuration.
Python 3.10+, Bun, Clang and LLVM LLD are required. Indexed array symbols, timers
and the frame-assembly app's OPC UA contract are verified. See
the native compiler guide for tested language
coverage, PLCopen import, downloadable source libraries, open-source math support
and remaining limitations.
Download original library files and a pinned dependency catalog with
codesys-build fetch-libraries --request libraries.json --output libraries,
then verify its captured evidence offline with
codesys-build verify-libraries --lock libraries/native-library-lock.json.
See native library support for request format,
version policy, supported containers and unresolved placeholder behavior.
An original binary .project can supply its saved ST sources, application name,
cyclic task and exact library redirections directly:
import { defineNativeConfig } from "codesys-build";
export const config = defineNativeConfig({
projectSource: { path: "original.project", sha256: "<64 hex characters>" },
task: "MainTask",
libraryCatalog: [
{ path: "Standard.compiled-library-v3", sha256: "<64 hex characters>" },
],
symbols: true,
});Run codesys-build build-native --python /path/to/python --linker /path/to/ld.lld.
No extracted ST directory is required. The completion receipt preserves the saved
application name, with runtime/<application>.app and matching .crc artifacts.
runtime/source.json retains the original source bodies and selected task;
runtime/project-intake.json records the original task/library evidence and all
excluded tasks, device and visualization services. This path builds one supported
saved cyclic user ST task; HMI, device configuration and physical I/O are outside
its scope. Missing dependencies or unsupported saved scheduling fail explicitly.
See binary project configuration.
