Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0aa9dfc
feat(compile): embed JSON→ST transpiler, retire xml2st binary (#843)
dcoutinho1328 Jun 8, 2026
2ae973a
chore(transpiler): prune dead code from Phase 2 cleanup (#851)
dcoutinho1328 Jun 8, 2026
6614f02
feat(versioning): shared app version + per-app product name
JoaoGSP Jun 8, 2026
e2f28b1
Merge remote-tracking branch 'origin/development' into feat/web-app-v…
JoaoGSP Jun 8, 2026
5422420
fix(alias): enforce unique aliases at write time + cascade rename + c…
thiagoralves Jun 8, 2026
fa11a1d
fix(alias): enforce alias-↔-location invariant on createVariable + sa…
thiagoralves Jun 8, 2026
623a91c
fix(alias): walk past intervening claims when auto-incrementing IEC l…
thiagoralves Jun 8, 2026
9c3b1cf
fix(graphical-editor): bind to exact-match variable on Enter instead …
thiagoralves Jun 8, 2026
9570fe5
style(graphical-editor): prettier reflow of useImperativeHandle deps …
thiagoralves Jun 8, 2026
ff9f48a
Merge pull request #850 from Autonomy-Logic/fix/alias-architecture
thiagoralves Jun 8, 2026
df273e9
chore(release): bump version to 4.2.1
JoaoGSP Jun 8, 2026
f2f37ec
Merge branch 'development' into feat/web-app-versioning
JoaoGSP Jun 8, 2026
cdb61e3
fix(versioning): import APP_VERSION in catalog-browser, not the global
JoaoGSP Jun 8, 2026
d12ce4a
Merge pull request #852 from Autonomy-Logic/feat/web-app-versioning
JoaoGSP Jun 8, 2026
59c4528
feat(transpiler): add OPENPLC_USE_NEW_TRANSPILER toggle, ship with le…
dcoutinho1328 Jun 8, 2026
c090a6f
Merge pull request #853 from Autonomy-Logic/feat/transpiler-toggle
thiagoralves Jun 8, 2026
0838ddd
fix(transpiler): resolve polymorphic TO_<TYPE> conversion functions t…
thiagoralves Jun 8, 2026
87d5ba5
style(transpiler): prettier reflow of TO_CONVERSION_TARGETS allowlist
thiagoralves Jun 8, 2026
ec7e675
Merge pull request #854 from Autonomy-Logic/fix/transpiler-polymorphi…
thiagoralves Jun 8, 2026
27f6387
chore(release): bump version to 4.2.2
thiagoralves Jun 8, 2026
6eb85d5
Merge pull request #855 from Autonomy-Logic/release/v4.2.2
thiagoralves Jun 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@ resources/st-compiler/**.spec

# External tool binaries (downloaded by scripts/download-binaries.ts)
# arduino-cli stays committed since we don't own its releases
resources/bin/**/xml2st
resources/bin/**/xml2st.exe
resources/bin/**/xml2st/
resources/bin/.binary-metadata.json
resources/bin/**/.binary-metadata.json
resources/strucpp/

# Playwright
Expand Down
17 changes: 8 additions & 9 deletions configs/webpack/webpack.app-info.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* App info configuration for webpack DefinePlugin
* Provides version from package.json and build date
* App info configuration for webpack DefinePlugin.
*
* APP_VERSION is no longer injected here: the shared About modal imports it
* from src/frontend/data/constants/app-version.ts — the single source of
* truth shared byte-for-byte with openplc-web, so the two IDEs always show
* the same version. This file now only provides the per-app product name
* (APP_NAME, "OpenPLC Editor") and the build date.
*/

import { resolve } from 'path'

// Read version from package.json
const packageJson = require(resolve(__dirname, '../../package.json'))

/**
* Get current date in YYYY-MM-DD format
*/
Expand All @@ -24,11 +24,10 @@ function getCurrentDate(): string {
* In CI builds, BUILD_DATE can be set via environment variable
*/
export function getAppInfoDefines() {
const version = packageJson.version as string
const buildDate = process.env.BUILD_DATE || getCurrentDate()

return {
APP_VERSION: JSON.stringify(version),
APP_NAME: JSON.stringify('OpenPLC Editor'),
BUILD_DATE: JSON.stringify(buildDate),
}
}
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "open-plc-editor",
"description": "OpenPLC Editor - IDE capable of creating programs for the OpenPLC Runtime",
"version": "4.1.4",
"version": "4.2.2",
"license": "GPL-3.0",
"author": {
"name": "Autonomy Logic"
Expand Down
2 changes: 1 addition & 1 deletion release/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-plc-editor",
"version": "4.1.4",
"version": "4.2.2",
"description": "OpenPLC Editor - IDE capable of creating programs for the OpenPLC Runtime",
"license": "MIT",
"author": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ describe('findHexInCompilationPath', () => {
describe('createEditorCompilerPlatformPort', () => {
function makeHandlers(overrides?: Partial<EditorCompilerHandlers>): EditorCompilerHandlers {
return {
handleTranspileXMLtoST: jest.fn(),
handleCompileArduinoProgram: jest.fn(),
handleUploadProgram: jest.fn(),
handleCoreInstallation: jest.fn(),
Expand Down Expand Up @@ -229,64 +228,6 @@ describe('createEditorCompilerPlatformPort', () => {
expect(log).toHaveBeenCalledWith(expect.stringContaining('lib install failed'), 'warning')
})

// ---- transpileXmlToSt — xml2stArgs forwarding (STRUCT drift regression) ----

it('transpileXmlToSt forwards args.xml2stArgs to handleTranspileXMLtoST verbatim', async () => {
// Regression guard for the editor/web STRUCT drift bug: the
// shared pipeline owns the xml2st flag set as an array of CLI
// tokens, and the editor adapter must thread that array into
// handleTranspileXMLtoST as the third positional arg — the
// handler then splices it straight into the spawned xml2st argv.
// Editor's local xml2st is trusted, so the adapter passes the
// array through verbatim (no filtering).
const handleTranspileXMLtoST = jest
.fn<
ReturnType<EditorCompilerHandlers['handleTranspileXMLtoST']>,
Parameters<EditorCompilerHandlers['handleTranspileXMLtoST']>
>()
.mockResolvedValue({ success: true, data: '' })
const tmp = mkdtempSync(join(tmpdir(), 'xml2st-args-'))
try {
const port = createEditorCompilerPlatformPort(
makeHandlers({ handleTranspileXMLtoST }),
makeContext({ sourceTargetFolderPath: tmp }),
)
// The handler stub never produces a program.st, so the readFile
// after the spawn-equivalent step throws — that's fine, we only
// care about the xml2stArgs argument forwarded to the handler.
await port.transpileXmlToSt({ xml: '<plc/>', xml2stArgs: ['--keep-structs'] }, () => undefined)
expect(handleTranspileXMLtoST).toHaveBeenCalledTimes(1)
const callArgs = handleTranspileXMLtoST.mock.calls[0]!
expect(callArgs[2]).toEqual(['--keep-structs'])
} finally {
rmSync(tmp, { recursive: true, force: true })
}
})

it('transpileXmlToSt forwards an empty xml2stArgs array verbatim', async () => {
// The adapter must not "helpfully" inject defaults when the
// pipeline asked for nothing — that would be the exact kind of
// silent drift the shared port contract exists to prevent.
const handleTranspileXMLtoST = jest
.fn<
ReturnType<EditorCompilerHandlers['handleTranspileXMLtoST']>,
Parameters<EditorCompilerHandlers['handleTranspileXMLtoST']>
>()
.mockResolvedValue({ success: true, data: '' })
const tmp = mkdtempSync(join(tmpdir(), 'xml2st-empty-args-'))
try {
const port = createEditorCompilerPlatformPort(
makeHandlers({ handleTranspileXMLtoST }),
makeContext({ sourceTargetFolderPath: tmp }),
)
await port.transpileXmlToSt({ xml: '<plc/>', xml2stArgs: [] }, () => undefined)
const callArgs = handleTranspileXMLtoST.mock.calls[0]!
expect(callArgs[2]).toEqual([])
} finally {
rmSync(tmp, { recursive: true, force: true })
}
})

// ---- uploadArduinoBoard — port wiring (regression for issue #5) ----

it('uploadArduinoBoard forwards args.port to the handler as communicationPort', async () => {
Expand Down
1 change: 0 additions & 1 deletion src/backend/editor/compiler/compiler-module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ describe('CompilerModule', () => {
expect(typeof compilerModule.arduinoCliBinaryPath).toBe('string')
expect(typeof compilerModule.arduinoCliConfigurationFilePath).toBe('string')
expect(Array.isArray(compilerModule.arduinoCliBaseParameters)).toBe(true)
expect(typeof compilerModule.xml2stBinaryPath).toBe('string')
expect(typeof compilerModule.strucppRuntimeDir).toBe('string')
})

Expand Down
115 changes: 77 additions & 38 deletions src/backend/editor/compiler/compiler-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
import { buildKnownPous, emitCompileErrorEvents } from '@root/backend/shared/library/program-build-helpers'
import { runProgramBuildPipeline } from '@root/backend/shared/library/program-build-pipeline'
import { loadStrucpp } from '@root/backend/shared/library/strucpp-runtime'
import {
fromSchemaShape,
type SchemaProjectData,
transpileToSt as runJsonTranspiler,
} from '@root/backend/shared/transpilers/st-transpiler'
import type { KnownPou } from '@root/backend/shared/utils/PLC/split-program-st'

/**
Expand Down Expand Up @@ -79,6 +84,7 @@

import { assertPathContained } from '@root/backend/editor/utils/path-containment'
import { getRuntimeHttpsOptions } from '@root/backend/editor/utils/runtime-https-config'
import { isNewTranspilerEnabled } from '@root/backend/editor/utils/transpiler-mode'
import { runCompilePipeline } from '@root/backend/shared/compile/pipeline'
import { mergeStrucppRuntimeIntoSkeleton } from '@root/backend/shared/compile/steps/merge-strucpp-runtime-into-skeleton'
import { readHalsFile } from '@root/backend/shared/firmware/hals-loader'
Expand Down Expand Up @@ -417,14 +423,6 @@
}
}

#executeXml2st(args: string[]) {
let xml2stBinaryPath = this.xml2stBinaryPath
if (CompilerModule.HOST_PLATFORM === 'win32') {
xml2stBinaryPath += '.exe'
}
return spawn(xml2stBinaryPath, args)
}

#executeArduinoCliCommand(args: string[]) {
let arduinoCliBinaryPath = this.arduinoCliBinaryPath
if (CompilerModule.HOST_PLATFORM === 'win32') {
Expand All @@ -433,6 +431,14 @@
return spawn(arduinoCliBinaryPath, args)
}

#executeXml2st(args: string[]) {
let xml2stBinaryPath = this.xml2stBinaryPath
if (CompilerModule.HOST_PLATFORM === 'win32') {
xml2stBinaryPath += '.exe'
}
return spawn(xml2stBinaryPath, args)
}

// ############################################################################
// =========================== Public methods =================================
// ############################################################################
Expand Down Expand Up @@ -486,8 +492,8 @@

checkStrucppAvailability(): MethodsResult<string> {
try {
const { getVersion } = loadStrucpp()

Check warning on line 495 in src/backend/editor/compiler/compiler-module.ts

View workflow job for this annotation

GitHub Actions / lint / Lint Check

Unsafe array destructuring of a tuple element with an error typed value
return { success: true, data: getVersion() }

Check warning on line 496 in src/backend/editor/compiler/compiler-module.ts

View workflow job for this annotation

GitHub Actions / lint / Lint Check

Unsafe call of a(n) `error` type typed value

Check warning on line 496 in src/backend/editor/compiler/compiler-module.ts

View workflow job for this annotation

GitHub Actions / lint / Lint Check

Unsafe assignment of an error typed value
} catch {
throw new Error('STruC++ not available. Run "npm run setup:binaries" to install it.')
}
Expand Down Expand Up @@ -2794,37 +2800,70 @@
return
}

try {
const generateXMLResult = await this.handleGenerateXMLfromJSON(sourceTargetFolderPath, projectData)
_mainProcessPort.postMessage({
logLevel: 'info',
message: `Generated XML from JSON at: ${generateXMLResult.data?.xmlPath as string}`,
})
} catch (error) {
_mainProcessPort.postMessage({
logLevel: 'error',
message: `Error generating XML from JSON: ${error as string}\nStopping debug compilation process.`,
})
_mainProcessPort.close()
return
}
if (isNewTranspilerEnabled()) {
// JSON → ST in-process via `st-transpiler`. Mirrors what
// `editor-compiler-platform-port.transpileToSt` does for the
// shared pipeline path, scoped down to the debug compile here.
try {
const ir = fromSchemaShape(projectData as unknown as SchemaProjectData)
const result = runJsonTranspiler(ir)
if (result.programSt === null || result.errors.length > 0) {
const message = result.errors.join('\n') || 'Failed to generate Structured Text'
_mainProcessPort.postMessage({
logLevel: 'error',
message: `${message}\nStopping debug compilation process.`,
})
_mainProcessPort.close()
return
}
for (const warning of result.warnings) {
_mainProcessPort.postMessage({ logLevel: 'info', message: warning })
}
await mkdir(sourceTargetFolderPath, { recursive: true })
const programStPath = join(sourceTargetFolderPath, 'program.st')
await writeFile(programStPath, result.programSt, 'utf-8')
_mainProcessPort.postMessage({ logLevel: 'info', message: `ST file generated at: ${programStPath}` })
} catch (error) {
_mainProcessPort.postMessage({
logLevel: 'error',
message: `Error transpiling JSON to ST: ${getErrorMessage(error)}\nStopping debug compilation process.`,
})
_mainProcessPort.close()
return
}
} else {
try {
const generateXMLResult = await this.handleGenerateXMLfromJSON(sourceTargetFolderPath, projectData)
_mainProcessPort.postMessage({
logLevel: 'info',
message: `Generated XML from JSON at: ${generateXMLResult.data?.xmlPath as string}`,
})
} catch (error) {
_mainProcessPort.postMessage({
logLevel: 'error',
message: `Error generating XML from JSON: ${error as string}\nStopping debug compilation process.`,
})
_mainProcessPort.close()
return
}

const generatedXMLFilePath = join(sourceTargetFolderPath, 'plc.xml')
try {
await this.handleTranspileXMLtoST(
generatedXMLFilePath,
(data, logLevel) => {
_mainProcessPort.postMessage({ logLevel, message: data })
},
['--keep-structs'],
)
} catch (error) {
_mainProcessPort.postMessage({
logLevel: 'error',
message: `Error transpiling XML to ST: ${error as string}\nStopping debug compilation process.`,
})
_mainProcessPort.close()
return
const generatedXMLFilePath = join(sourceTargetFolderPath, 'plc.xml')
try {
await this.handleTranspileXMLtoST(
generatedXMLFilePath,
(data, logLevel) => {
_mainProcessPort.postMessage({ logLevel, message: data })
},
['--keep-structs'],
)
} catch (error) {
_mainProcessPort.postMessage({
logLevel: 'error',
message: `Error transpiling XML to ST: ${error as string}\nStopping debug compilation process.`,
})
_mainProcessPort.close()
return
}
}

try {
Expand Down Expand Up @@ -2852,7 +2891,7 @@
_mainProcessPort.postMessage({
logLevel,
message: data,
...(compileError ? { compileError } : {}),

Check warning on line 2894 in src/backend/editor/compiler/compiler-module.ts

View workflow job for this annotation

GitHub Actions / lint / Lint Check

Unsafe assignment of an error typed value
})
},
{ hasCBlocks, pous: knownPous, libraries, missingLibraries },
Expand Down
Loading
Loading