Skip to content

v0.30.0

Latest

Choose a tag to compare

@github-actions github-actions released this 02 Nov 17:39
· 197 commits to main since this release

[0.30.0] - 2026-11-02

Headline: Catch before the build what fails at build time or in production, and merge Mendix models from the command line. New security lint rules report mxbuild's CE2729 and the page data sources that fail for a role at Production security, lint --baseline reports only new findings, and mxcli git merge runs Studio Pro's own merge engine and lets you review each conflict before it lands. Alongside it: optional page parameters, a configurable microflow layout, --mcp working through Studio Pro's own MCP server alone, and faster catalog refreshes and describes on large projects.

Added

  • SEC012 reports mxbuild's CE2729 before a build (ako#936) — a page a user role can reach that shows an attribute, or lists an entity, the role may not read. Reachability is per user role and measured against mxbuild: menu items the role may view, its home page (checked even when it may not view it, but not walked further), show-page actions, snippet calls, and microflows and nanoflows it may execute with their sub-microflows. On JTSBootLogboek with an access-less role it reports the same 27 attribute and entity errors mxbuild does, and nothing on FactoryManagement or JTSBootLogboek as they are, where mxbuild reports none. Association reads, System entities and URL-only pages are not covered yet.
  • SEC011: a page data source whose XPath crosses an association the page's roles cannot read (ako#945, #1337, #1471) — at Production security such a page opens to an error dialog (No access rights for Shop$Account/Shop.Order_Account) while mxbuild, check and lint were all clean. The rule checks each association step from the entity it starts on, per user role that can open the page, and reports it as an error with the member to grant. Measured on 11.15: an association is checked only where it is a member (its FROM entity, and its TO entity when the owner is Both), and an XPath inside an entity access rule is not checked at all, so neither is reported.
  • MPR012 covers every widget measured as CE0582 under the React client (ako#564) — besides the static and dynamic image it now reports the classic drop-down, the legacy data grid and the reference selector a project already holds, each with its replacement (combobox, Data grid 2). The set is measured on 11.16 with mxbuild, controls included: the navigation tree, menu bar, file manager, scroll container, image uploader and the common input widgets build clean and stay silent. The rule is renamed ReactUnsupportedWidget; its ID is unchanged.
  • DROP over --mcp uses Studio Pro 11.16's ped_delete_document (ako#1851) — DROP of a constant, enumeration, microflow, nanoflow, page, workflow or business-event service calls the native delete tool. A server without it (Studio Pro before 11.16) gets an error naming the version it needs.
  • mxcli lint --baseline <file> reports only new findings (ako#1328, #1455) — record a baseline with --update-baseline, commit it, and later runs report only the findings it does not have, with an exit code that follows them alone and a N new, M already known, K fixed summary on stderr. Findings are matched on rule and document, not message, so rewording a rule does not make its findings new; a scoped run (-m, -d, -r) reports new findings but cannot update the baseline.
  • Lint permission rows name their access rule (ako#1437, #1539) — permissions() and permissions_for() rows carry access_rule_id and access_rule_index, so a rule can count an entity's access rules per setting or list one rule's module roles exactly, instead of guessing by grouping on XPath, default and rights. The catalog schema moves to version 31, so a cached catalog is rebuilt once.
  • Lint rules can read the data guideline rules asked for (ako#1453, ako#1301) — a microflow's excluded and apply_entity_access, an enumeration's values with their captions (so a rule can resolve getCaption(Module.Enum.Value)), constant_values() for the run-configuration values, and project_settings() (default language, OQL version, Java version, hash algorithm, time zone, rounding, decimal scale, optimistic locking, foreign keys, web client). The catalog schema moves to version 30, so a cached catalog is rebuilt once.
  • Configurable microflow and nanoflow layout (ako#1832) — a committed layout-config.yaml (in .claude/ or next to the .mpr) chooses how create microflow/create nanoflow and mxcli layout flows place what a script leaves unpositioned: preset happy-line (the default, unchanged) or true-right-false-down, and the options if-else: true-right-else-column (ELSE stacked as a column under its decision), case-split: first-right-rest-columns (a CASE's or type split's first outcome on the line, the rest as columns), guard: true-right, wrap: off and max-row-width. A flow laid out in a non-default style records it as an @mxcli-layout: line in its documentation, which DESCRIBE and create or modify keep using for that flow, so its description stays free of @position when the project style changes. mxcli layout config shows the style in force; mxcli layout flows --style <preset> tries one. Without a config file nothing changes: measured on 137 flows, the geometry and DESCRIBE output are identical to before.
  • Optional page parameters (#1640, ako#1649) — Params: ( $Sale: Shop.Sale optional ) is Studio Pro's unticked Required (Forms$PageParameter.IsRequired = false, Mendix 11.5+): the page can be opened with no argument, from a menu item for example, where a required parameter is CE1571. create page, alter page … add parameters and describe page all spell it. describe navigation no longer invents (Sale = $currentObject) for a menu item opening such a page, which made its output one exec refused (MDL-PAGEARG01) and left the navigation profile unchangeable from MDL. Below 11.5 optional is refused (MDL-VERSION01), and a snippet parameter cannot be optional (MDL087).
  • mxcli git merge <ref> — model-aware merging from the command line (#1669) — git merge cannot merge Mendix models: the .mpr is SQLite and every .mxunit is BSON, so two sides changing the model always meet a binary CONFLICT (content) (measured in the git lab: both files, every time). Studio Pro resolves that with a model-aware merge, and the same engine ships as a command — mx merge BASE MINE THEIRS over the three revisions' project files, which runs on Linux devcontainers too. mxcli git merge <ref> runs git merge first (a merge git resolves itself is annotated like mxcli git commit; a merge stopped on text conflicts resumes once those are resolved and staged), and on a model-file conflict checks the base, ours (a clean checkout of HEAD — never the working tree mid-merge, where the engine chokes) and theirs revisions out into temporary worktrees and hands the three projects' .mpr files to the engine, mx resolved for the project's Mendix version (PATH, the mxcli cache, the Studio Pro installation): everything the engine auto-resolves is copied back from MINE, hash-checked, staged and committed as the normal two-parent merge commit with the mx_metadata note — and the engine owns the .mpr, so a unit one side added while the other changed existing ones is kept, not dropped, and a deletion it resolved removes the unit file with it; the engine's own -v conflict list (one JSON array per run, ManualResolutionRequired vs AutoResolved) decides which units the review material and plan template cover, with git's conflicted-file list as the fallback; a conflict the engine reports as ManualResolutionRequired (exit 2) is a review step, never a silent choice: the merge aborts leaving the working tree exactly as before, and both sides' changes are printed unit by unit at property level (from mx diff), with a plan template (<mpr>.merge-plan.json, never staged by mxcli git commit) beside the project — the human re-runs with --plan <file> to resolve per unit over the engine's own result (the engine runs again and the plan decides only the units it left open — everything it merged stays merged; each planned unit takes ours or theirs whole, its conflict marker is cleared and the .mpr index repaired from the files; a plan naming more or fewer units than the engine left open is refused; modified-on-both units only, added/deleted/moved goes to Studio Pro), or with --ours/--theirs to take one side of every conflicted unit — hash-gated: when the sides' document sets differ (one side added or deleted a unit) the commit is refused and the merge aborted, because a whole side would orphan the other side's documents — or resolves in Studio Pro (Version Control › Merge Changes Here), which remains the tool for mixed resolutions; any other engine exit (its 129, an mx that will not start) is reported as the engine failing and the merge is aborted cleanly, never steered into a decision. Non-model conflicts (text sources) are left in place like plain git: resolve them, git add them, re-run the command. Refused while Studio Pro has the project open. Docs: "Working Outside Studio Pro" › Merging branches.
  • Lint rule CONV021 (CreateChangeThenCommit) — warns when a microflow's Create or Change object activity without commit is directly followed by a Commit of the same object, and suggests the one activity that does both: Commit set on the create/change (Yes, or Yes without events for a commit without events; Refresh in client kept). It stays quiet when merging could change behaviour: another flow into the commit, an error-handler flow between them, or custom error handling on either.
  • Expression check E015: a String function's result compared with empty — if trim($Value) != empty passes check, exec and mx check and is always true: trim, toLowerCase, toUpperCase, substring, replaceAll and toString return '' for an empty input, never empty (measured on the Mendix 10.18.5 runtime). check now warns, and stays quiet on the trim($v) != empty and trim($v) != '' guard idiom, where the '' comparison decides.

Changed

  • SEC011 names the widget whose data source fails on every page — it uses the catalog's new WidgetName (ako#1879) instead of guessing; before, a page with two data widgets over the same entity was reported as "A data source on page …" with no way to tell which grid to fix.
  • Concord support is removed; --mcp-check and --mcp-run use Studio Pro's own MCP server — the optional second MCP server (Concord) and the --mcp-concord, --mcp-concord-dial and --mcp-save flags are gone. PED writes each change to disk as it lands, so there is nothing to save. --mcp-check runs a project-wide ped_check_errors (Studio Pro 11.15+), and --mcp-run starts the app through the mendix_app handler and prints its URL (11.16+).
  • The first command on a project says it is building the unit index (ako#1806) — after an upgrade to the unit-index-2.idx format, or on a fresh clone, one command opens every unit file to build the index, which on Windows is a visible stall. It now prints one line on stderr (Building the unit index for this project's N units (once; …)); -q / MXCLI_QUIET keep it silent.
  • git doctor judges only core.autocrlf among the local git config — the mendix.commits-since-gc and mendix.lineEndingResetDone keys are Studio Pro's own bookkeeping (measured on a lab repository), so a repository without them is no longer reported, and --fix no longer writes them.

Fixed

  • The catalog's XPath rows name their widget (ako#1879) — xpath_expressions.ComponentId for a page or snippet data source was a Go byte dump ({0 [57 221 …]}) and nothing tied the row to its widget. It is now a GUID, and the new WidgetId / WidgetName columns (widget_id / widget_name in xpath_expressions()) name the widget by the id widgets gives it. The catalog schema moves to version 32, so a cached catalog is rebuilt once.

  • create or replace page can clear a data grid column caption (ako#1877) — Caption: '' over a stored caption reported "Unchanged page" and kept it, because the translation carry added the stored text back to an empty header written with no translation. The header is now written as one empty translation, which the carry leaves alone; an empty translation where the stored text has none is dropped, so a Studio Pro empty caption still rewrites as unchanged. This also repairs pages that still carry a leaked column name (#535).

  • A custom-content data grid column with an empty caption no longer shows its MDL name as the header (ako#535) — through the generic pluggablewidget 'com.mendix.widget.web.datagrid.Datagrid' form, column colTriage (Caption: '') { … } (or no caption) wrote colTriage as the header text, which end users saw; Mendix stores no name for a column. It now writes an empty header, as the keyword datagrid form already did; it builds clean (11.15.0) and Data grid 2 renders it blank.

  • --mcp http://localhost:<port>/mcp reaches Studio Pro on a Windows machine with Docker Desktop (#1684, ako#1833) — without --mcp-dial, mxcli dialled host.docker.internal whenever that name resolved, taking it to mean "inside a container". Docker Desktop for Windows puts the name in the hosts file, pointing at the LAN address, and Studio Pro listens on loopback only, so the connection timed out (dial tcp <LAN IP>:7782: i/o timeout) on the very machine Studio Pro runs on. mxcli now dials the URL's own host first and tries host.docker.internal only when that fails, so the same command still works from a devcontainer. --mcp-dial is unchanged: when given, it is the only address tried.

  • --mcp-trace and --mcp-verbose list the PED calls (#1684, ako#1833) — they printed each MDL statement but never a tool call, on exec, -c, the REPL and mxcli mcp capabilities alike. A run that sent every write to Studio Pro read as one that sent none, which is how a report concluded that exec --mcp wrote the .mpr instead. The backend the CLI builds for --mcp now gets the tracer too, so each statement is followed by its → ped_… calls.

  • A PED tool whose handler throws is reported as a failure — Studio Pro answers Error executing tool handler: … with isError:false (measured on 11.16.0), which a caller trusting the flag read as success; the MCP client now classifies that text as an error.

  • describe and the source catalog no longer decode every document of a type to find one (ako#1850) — describing a snippet decoded every snippet of the project, a nanoflow every microflow and nanoflow, a page every page, and so did resolving the snippets, pages, layouts and flows a description names. A source catalog build describes every document, so it paid that once per document: on FactoryManagement a first refresh catalog full source took 33 s and 145 s of CPU, and now takes 19 s and 38 s, with identical text for all 3,718 documents. create nanoflow likewise decodes only the nanoflows of its own name.

  • Refreshing the catalog after a model edit no longer spends seconds on widget definitions (ako#1848) — refresh catalog re-read every widget package on every run, and on FactoryManagement it took about 3 s of a 5 s refresh on Windows. The step is now skipped when no .mpk or .def.json file has changed since the last refresh by the same mxcli build, and reading a widget's editor rules is no longer quadratic in the size of its editorConfig.js (Maps' took 0.5 s and now takes 15 ms, with identical rules on 256 widgets). Two widgets that derive the same keyword, such as KeyboardShortcut.mpk and itvisors.KeyboardShortcut.mpk, no longer overwrite each other's definition: the pluggable one keeps KEYBOARDSHORTCUT and the other becomes CUSTOMKEYBOARDSHORTCUT. Measured on a Mac, a refresh after one edit went from 2.8–3.2 s to 1.0–1.4 s.

  • MPR012 follows the app's Web UI client (ako#1287, #1226) — a Mendix 11 app can still run the classic client, where the legacy static and dynamic image widgets build clean, but MPR012 warned on each of them that "mxbuild reports CE0582". On the classic client or in migration mode it is now an info-level note that the widget needs converting before the app switches; under the React client it stays a warning.

  • Every mxcli init --tool <x> installs the bundled lint rules, and lint says when it runs without them (ako#1292, #1242) — only claude and opencode wrote .claude/lint-rules/, so init --tool cursor (or aider, continue, copilot, vibe, windsurf) left mxcli lint running the built-in Go rules alone, silently. lint and report now print a warning when no .claude/lint-rules/ is found, naming how many rules run instead.

  • show references to an entity, attribute or association lists the message definitions that expose it (#1781) — a message definition (the pre-11.15 collection, MESSAGE_DEFINITION_COLLECTION, and the 11.15+ document, MESSAGE_DEFINITION) was not walked for references, so impact analysis called an exposed entity or attribute unused. The entity edges are mapping, the attribute and association edges member, as for an import or export mapping.

  • A pluggable widget's hidden expression property is stored as Studio Pro stores it (ako#548) — a property the widget's editorConfig hides is written at its declared default, and for an Expression-typed one that default went into PrimitiveValue as well as Expression (every combo box's customEditabilityExpression, PrimitiveValue: "false"). Studio Pro leaves PrimitiveValue empty on all 939 Expression-typed values of ako/TestApp and PedApp; mx check accepted both, so the cost was a widget that differed from Studio Pro's.

  • A source catalog build describes a project widget the same way every run (#1799) — refresh catalog full source sometimes wrote a pluggable widget in the generic pluggablewidget '<id>' form instead of its own keyword (vegachart …), and since #1802 an incremental build kept that row until a force. Each parallel describe loaded its own widget registry, and with one definition that never counts as current (hand-made, or its .mpk removed), each load rewrote every .def.json while the other describes read them. The describes now share the build's registry, and a definition is replaced by rename, so no reader sees a half-written file.

  • A rewrite gives a stored list the marker Studio Pro writes (ako#1640) — a list an older mxcli stored with the wrong typed-array marker kept it through every later rewrite, because a rewrite copies much of a stored document as bytes (an unchanged pluggable widget's Type and Object, an existing element's unchanged properties). A Data grid 2's SortBar.SortItems stayed 3 where Studio Pro stores 2, and drop page plus the same MDL was the only way out. Every write now applies the measured owner/key markers to the whole unit, and lists without a measured marker are left as they are. A populated nanoflow parameter list (on a call nanoflow action or a nanoflow data source) is now written with 2, as Studio Pro does. mx check never reported either; the effect was a document that differed from Studio Pro's at the value level.

  • MPR v2: a unit written twice in one modelsdk write transaction commits the second write — writing a unit A → B → A in one WriteTransaction committed B. The second write was compared with the committed file rather than with the transaction's own first write, which sits in a temp file until commit, so it matched A and was skipped as unchanged. It is now compared with the pending write. No mxcli command or modelsdk call writes a unit twice in one transaction, so this was reachable only by using WriteTransaction directly.

  • Integer properties are stored as 64-bit integers, as Studio Pro stores them (ako#1264, #675) — every Integer property mxcli wrote, a page's canvas and popup sizes among them, was a 32-bit BSON integer where Studio Pro writes 64-bit, so an edit of a Studio Pro document changed the width of all its numbers. mx check accepts both. A document an older mxcli wrote is not migrated, and reads accept either width. Reading a mapping's ancestor binding (LevelOfParent) from a Studio Pro document no longer gives level -1.

  • check catches a template brace the build rejects (ako#1397, #1515) — 'Item {number} not found' in a log message, or Body: template '{}' in a REST call, passed check and failed the build with CE0712. MDL-TEMPLATE02 now reports mxbuild's code for every template slot, and exec refuses the same texts. A workflow text or caption with an escaped {{1}} is no longer refused.

  • check warns on a replaceAll pattern that ends in a lone backslash (ako#1397, #1515) — it builds, then fails on every call at runtime. MDL-REGEX01 covers replaceAll, replaceFirst and isMatch. The replacement is literal text in Mendix, so a backslash there is not flagged; mxcli syntax microflow expressions explains both.

  • A catalog refresh after a save rebuilds only what the save changed (ako#1804) — every refresh rebuilt every catalog table from every document, about 4.5 s of decoding and inserting on a 4,753-document project, and more on Windows, after any edit. The cache now records which document each row came from. The next refresh starts from the cached catalog and rebuilds only the changed documents and the documents that reference them, reading and decoding only those. A refresh after one edit on that project went from 5.6–8 s to 0.7 s end to end, with or without the source index. It falls back to a full build when a folder, module, settings, security or navigation document changes. Tests compare every table with a forced full build after each of 19 kinds of edit.

  • show references finds flows that branch on an entity (ako#1804) — an inheritance split (split type $x when Module.Entity) names the entity only on its outgoing flows, and the catalog recorded no edge for it, so show references to and impact on the entity missed the flow. It is now a split edge. Found by the new ref coverage trace, which checks that every document a catalog pass reads is reachable through refs.

  • mxcli git commit and git merge say what they did — the staged-files summary lists what the commit holds, not the files just unstaged, and a merge git refuses to start (a dirty working tree, no identity) reports git's own reason instead of git merge side: Updating 5d6b73c..2c3fd47.

  • The SessionStart bootstrap script installs mxcli under Git Bash on Windows — it built the asset name from uname -s (MINGW64_NT-10.0-26200), requested a binary that does not exist, and the session started without mxcli.

Security

  • golang.org/x/net v0.60.0 -> v0.62.0 for CVE-2026-97045 (GO-2026-6866) and CVE-2026-78661 (GO-2026-6860) — both are in golang.org/x/net/html, which mxcli does not link: at v0.29.0 go list -deps ./cmd/mxcli has no x/net/html, and source-mode govulncheck ./... reports neither as called. Scanners still flag them because the stripped release binary only tells them the module version. The bump also raises x/crypto v0.58.0, x/sys v0.49.0, x/text v0.43.0, x/sync v0.24.0 and x/term v0.47.0, which x/net v0.62.0 requires. After it, govulncheck ./... (Go 1.27.3) reports nothing called and one module-level entry, GO-2026-5932 (x/crypto/openpgp, unmaintained, no fix), whose package mxcli does not import.

Full Changelog: v0.29.0...v0.30.0