Skip to content

Sync ako/mxcli: integrate nine contributor PRs (#1574 #1575 #1572 #1570 #1563 #1561 #1568 #1566 #1368) with review follow-ups - #1589

Merged
ako merged 29 commits into
mendixlabs:mainfrom
ako:main
Oct 20, 2026
Merged

ako merged 29 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream. The 19 commits come from one fork PR, ako#1490. It integrates nine open contributor PRs by @stagknee and adds review follow-ups on top.

Each PR's own commits are kept unchanged. When this merges, GitHub should mark the nine PRs as merged, because their head commits will then be in main.

Integrated PRs

PR Change
#1574 check without a project accepts a Data grid 2's own settings (#1573, check half)
#1575 describe prints Data grid 2 / Gallery settings by widget key, non-default values only (#1573, describe half)
#1572 describe names the entity an association data source reaches as its context (#1571)
#1570 check -p / exec resolve the pages and flows a navigation profile or menu names (#1569)
#1563 A bare enumeration name is accepted as a return type in exec, as check does (#1562)
#1561 A JavaScript action's parameters are written the way mxbuild generates them (#1560)
#1568 A quoted Editable: '<expr>' is stored as conditional editability (#1567), and editable: false counts as Never in MDL-WIDGET31
#1566 run --detach on Windows no longer opens a console window per child (#1484)
#1368 mxcli test finds test files in subfolders, skips _ folders, and keeps going past unreadable ones (#1363)

The only merge conflict was in CHANGELOG.md, resolved by keeping both sides.

Follow-ups on top

Open points

Validation

  • Build, Test & Lint was green on the fork at a1fb0d8a.
  • All diffs were reviewed statically. They have no workflow, build, dependency or hook changes, and no hidden Unicode

stagknee and others added 29 commits October 15, 2026 11:00
ParseTestDir skipped every sub-directory, so `mxcli test tests` found nothing
when suites live in tests/<Module>/*.test.mdl. It now walks the tree in lexical
order, skipping hidden folders; the "no tests found" hint walks the same way.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nd nothing (#1363)

Folders starting with `_` below the folder you name are skipped, like
dot-folders (the Go toolchain's rule), so a deliberate positive-control suite
in tests/unit/_control stays out of the default run; naming it runs it. The
skipped folders are reported on the line after "Found N test(s)" in a run and
in --list, and in the "no tests found" hint, so nothing disappears silently.

`mxcli test <dir> --list` over a tree with no tests now prints the reason on
stderr. Its exit status stays 0; a real run with no tests already exits 1.
ListTests takes a second writer for that hint.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Review of #1368. With discovery recursive, one unreadable folder (a locked
folder, a root-owned one in a mounted volume) made ParseTestDir return a hard
error and dropped every test in the other folders. Only an error on the root
is fatal now; below it the error is that folder's: it is added to FileErrors
(Dir set), the run goes red through it, and the readable tests are kept.
The messages say "could not be read" for a folder, not "failed to parse".

Also:
- testFilesMTime (--watch) walks with walkTestTree, so hidden and _ folders
  and the root rule cannot drift from discovery.
- Errors, FileErrors and tests report paths under the name the user typed when
  the root is a symlink, not the resolved target.
- Document that discovery is fully recursive (point at the tests folder, not
  the project root) and that symlinked subfolders are not followed.

Tests inject the walk error through a seam so Windows and root CI cover the
logic; the real chmod 000 test skips there.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ild generates them

`create javascript action ... (Url: String, ReadOnly: Boolean)` wrote
`export async function JS_Open(Url, ReadOnly)`. mxbuild and Studio Pro
regenerate the signature and the JSDoc from the model with the first
character of each parameter name lower-cased and keep the body, so after a
build the signature read `JS_Open(url, readOnly)` and a body using `Url`
threw ReferenceError at run time. check, mx check and the build were all
clean. Rewriting a Studio Pro-authored action through MDL did the same in
reverse: `Geocode(address, ...)` became `Geocode(Address, ...)` over a body
that still read `address`.

JavaScriptParameterName lower-cases the first rune only, which is what
mxbuild 11.12.5 does (URL -> uRL, MyID -> myID, Some_Name -> some_Name,
XMLData -> xMLData, _Lead unchanged). GenerateJavaScriptSource uses it for
the signature and the @PARAM tags. The model parameter keeps its declared
name, so describe prints it as declared and describe -> exec is stable, and
bodies are not rewritten.

Tests: the measured table, the generated signature, a rewrite over a Studio
Pro-style file, and goldens of what mxbuild left for two actions. A check
warning for bodies that use the declared case is a separate change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A bare `returns Module.Name` is recorded by the parser as an entity
reference. exec retried it as an enumeration for parameters but not for the
return types of a microflow, a nanoflow or a rule, so a script that
`check -p --references` accepted failed with "entity 'M.E' not found for
return type". For a rule, check refused it as well ("a rule must return a
Boolean or an enumeration, not Entity").

The three create handlers now resolve a return type through the helper the
parameter path uses (entity first, then enumeration), and the rule check
applies the same retyping before validateRule. The stored return type equals
the one `returns enum M.E` stores. An unknown name now reports "entity or
enumeration 'M.X' not found for return type".

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The detached run was created with DETACHED_PROCESS, which gives it no
console, so Windows allocated a new, visible console for every console
program it started (java, mxbuild, gradle). Create it with
CREATE_NO_WINDOW instead: the run gets one console with no window, and its
children inherit it. Windows ignores CREATE_NO_WINDOW when it is combined
with DETACHED_PROCESS, so the old flag is dropped, not supplemented.

The new test starts a helper through setDetachAttrs and has the helper and a
console child of it report their console (GetConsoleCP, GetConsoleWindow,
GetConsoleProcessList): the run must have a console without a window, and
the child must share it.

Fixes mendixlabs#1484

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…itability

`Editable: '$currentObject/Locked = false'` on an input widget passed check,
exec said "Created page", and the widget was stored `Editable: Always` with no
ConditionalEditabilitySettings, so a field meant to be locked stayed editable.
`Visible: '<expr>'` was stored as a conditional visibility: its consumer
(pages.StaticVisibleExpression) returns any other string as the condition, while
Editable's (pages.CanonicalEditability) knew only Always|Never|Conditional and
the builder skipped every other string without a word.

New pages.StaticEditability is the Editable counterpart: an enum word, a
boolean (false is Never, true is Always) or, for any other string, the
condition. A list view's and a grid column's Editable stays their own plain
bool. check now refuses what still cannot be stored (a misspelt word, a
quoted text that is not an expression, `Conditional` with no condition) as
MDL-WIDGET19 naming the accepted spellings, and a quoted condition gets the
checks the bare one gets (MDL-WIDGET13, MDL-BUTTON02, MDL-WIDGET41,
MDL-WIDGET20). describe prints the stored condition bare, so describe -> exec is
stable. ALTER PAGE set (Editable: '<expr>') keeps its loud refusal.

Control: same script on the pre-fix binary stores Always/null for the quoted
widgets; with the fix they are byte-equal (ids stripped) to the bare spelling
and mx check reports 0 errors on Mendix 11.12.5.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r pluggable and data view

Follow-up to the quoted Editable expression fix. MDL-WIDGET31 still tested the
raw string for "Never", so an input marked `editable: false` (now Never) in a
read-only list view kept earning the warning that tells it to mark itself
Never; it reads the same resolved value as the builder now.

Tests added for the shapes a review asked about: a pluggable widget that
declares Editability is refused the same unstorable values by MDL-WIDGET19, and
a data view carries the quoted condition exactly as the bare one (its static
Never/Always is stored from its own ReadOnly flag, which this change does not
touch). CHANGELOG, finding record updated to match.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… names (mendixlabs#1569)

A navigation profile points at documents in six places: the home page (and one
per user role), a home flow, the login page, the not-found page and every menu
item at any depth. A standalone menu document carries the same menu items.
Nothing resolved any of them, so `check -p` said "Check passed!", `exec` wrote
the profile, and only `mx check` objected (CE1613 "The selected page ... no
longer exists.", once for the home page and once per menu item). CREATE OR
REPLACE NAVIGATION rewrites the whole profile, and the menu builder stores a
target by name and never fails on it.

check -p now reports each unresolved target with the profile or menu document,
the menu item path and the missing name, using the lookups every other page,
microflow, nanoflow and entity reference is resolved with. exec runs the same
check in the handler before it reads or writes anything, so `exec --no-check`
is covered too. A menu document stored as excluded only warns.

A target the script creates resolves. One created further down also resolves in
a run that rolls back (plain exec, check), as a call to a flow created further
down does (#1405): upstream accepts a navigation written before its pages and
mx check reports 0 errors for it, so refusing it would break a working script.
exec --continue-on-error, the REPL and -c cannot roll back and refuse it, from
validateForwardDefRefs.

Measured on a Mendix 11.12.5 app: 4 x CE1613 before for the reported script,
refused by check -p and exec after, and a valid navigation (existing pages, a
Marketplace-module page, a page created in the same script) still passes check
and exec with 0 errors from mx check.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…its context (mendixlabs#1571)

`describe page` printed the association as the context entity of a data
container bound over an association:

  dataview dvInner (DataSource: $currentObject/Shop.Order_Customer) {
    -- Context: $currentObject (Shop.Order_Customer)   <- the association

dataSourceEntityContext used the datasource reference as the entity for every
kind but a flow. The reference means a different thing per kind: the entity for
a database source, the flow for a microflow/nanoflow source (fixed in
6e59e0e), the association for an association source. The page was always
correct; only the comment, and the association-name spelling that depends on the
context, was wrong.

The entity an association source reaches is stored on its last hop
(EntityRef.Steps[last].DestinationEntity), which is already right from either
end of the association, for a reference set, and for a specialization of the
association's end, so it is read instead of re-derived from the domain model.
associationSourcePath saw each step's destination but kept only the
association names; rawDataSource.Destination now carries the last one, set
where both association sources are built (Forms$AssociationSource / data view
and the pluggable XPath source). A source with no stored destination keeps the
reference, as the flow fix does. The `DataSource:` text is unchanged.

Tests: owner-to-child reference, the reverse direction, reference set with its
selection variable, specialization, two hops, a page-parameter start, the
pluggable source, the no-destination fallback, and database/flow controls.
Against upstream/main the association cases fail with the reported symptom.
Verified on a Mendix 11.12.5 project: both directions describe the entity,
describe -> exec reports "Unchanged page", mx check 0 errors.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ndixlabs#1573)

`mxcli check` without `-p` warned, for every setting a Data grid 2 takes by
its own key,

  widget `dgAttr` (datagrid) property `configurationAttribute` is not
  recognized and will be silently dropped on write [MDL-WIDGET07]

although `exec -p` stores it and `check -p` accepts it, and it refused an
Expression-typed one (`rowClass`, a column's `columnClass`) as an MDL-WIDGET42
ERROR, "a plain-value property cannot hold the expression". A script exec takes
therefore failed `make check-mdl` (exit 1 with 2 errors, measured), and so
would the output of `describe page` once it prints such settings.

DATAGRID has no embedded definition: its .def.json is generated from the
project's own package, and an embedded one would take precedence over that
package on every write (RefreshWidgetDefinitions skips a widget id the registry
already holds), so adding one would change the write path to fix a check.
Without a project the keyword resolved to no definition at all and was judged
against the vocabulary of the built-in widgets.

The check now reads the property list of the Data grid 2 template mxcli embeds,
for the grid's own keys and, for a `column` inside one, the item type of
`columns`. A key the template declares (or the `<key>Params` of a text
template) is accepted, and its declared type decides MDL-WIDGET42. The template
is the oldest-supported baseline, so a key it does not declare is still
reported by MDL-WIDGET07, with a did-you-mean from the template's names, but as
what is true without a project: it cannot be verified, and is dropped on write
unless the installed widget declares it. The Data grid 2 template carries the
same 40 grid keys and 21 column keys as the 11.12.5 package. With a project,
and for every other widget, nothing changes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r widget key (mendixlabs#1573)

A pluggable widget takes every one of its settings under the key its package
declares, and exec stores them:

  datagrid dg (DataSource: database Shop.Ord, configurationStorageType: localStorage,
               itemSelectionMethod: rowClick, refreshInterval: 30)

`describe page` printed only the properties MDL has a keyword for (PageSize,
Pagination, Selection, onClick), so executing the description wrote the
widget's default over each of the others. Measured on Mendix 11.12.5: a grid
with 22 settings changed came back with the 4 that have keywords, with
`mx check` at 0 errors throughout, and a Studio Pro-authored grid lost its
`onClickTrigger: double`.

The describers for datagrid2 and gallery now also print every setting whose
stored value differs from the default, under its key: enumerations and plain
values, text templates with their `{N}` parameters, expressions, attributes, a
column's own settings (minWidth, minWidthLimit, allowEventPropagation, …) and
the empty-state placeholder slot. The default is read from the widget's own
stored definition (Type.ObjectType, and the item type of `columns`), not from a
list. It is not always the schema's DefaultValue: four text templates have ''
there and store the text the widget ships for them, so a text template counts
as default when it holds that text, and a grid that sets none of the settings
describes byte for byte as before (16 pages of a Studio Pro-authored project and
16 of the committed PedApp: the one that differs is a marketplace page, by the
setting it had been losing).

An Expression prints unquoted, except an expression that is only a string
literal, which would be read back as the string and stored without its quotes.
An attribute prints bare where exec resolves a bare name (inside a data
container) and qualified where it would not. A setting with no spelling (an
attribute reached over associations) is named in a `-- NOT re-executable`
comment. Selection-typed keys are carried by `Selection:` and not repeated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s without a project (mendixlabs#1573)

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…lery's settings by their widget key (mendixlabs#1573)

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…data source reaches as its context (mendixlabs#1571)

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…gation or menu document names (mendixlabs#1569)

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…in exec, as check does

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…parameters the way mxbuild generates them

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…r in MDL-WIDGET31, cover pluggable and data view

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…a console window per child

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…t cannot read (#1363)

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
The no-project check (mendixlabs#1574) and the describe-by-key tests
(mendixlabs#1575) loaded the template from sdk/widgets, while every other executor
path, and exec itself, reads modelsdk/widgets. The two datagrid.json copies
declare the same keys today, so this changes no result; it makes check
judge a key against the copy exec writes from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…cefully

mendixlabs#1566 gave the run a hidden console (CREATE_NO_WINDOW)
instead of none, so "console-less" no longer described it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…check

Review note on mendixlabs#1570: the len(known) == 0 guard reads like a
gap. It is the price of not turning a failed read into a false "not found".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
Part of 4a3b6ce moved this test fixture to modelsdk/widgets as well. In
the full executor package run the grid it builds then carries a non-default
columnsFilterable, so three TestDataGridByKey_* tests fail; the JSON files
differ only by AllowUpload, so something in the package changes what the
modelsdk copy yields. The fixture goes back to what mendixlabs#1575
shipped with; the production check keeps the modelsdk import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
mendixlabs#1570 makes exec refuse a navigation target it cannot find,
and the doctype example named the login page Administration.login. The page
is Administration.Login, as everywhere else in the repo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
b43f1d7 renamed the login page to Administration.Login, but the test
project has no such page in either case: CI refused it as "page not
found: Administration.Login" while Administration.Account_Overview
resolved. mx check never flagged the missing login page, so the example
had named a nonexistent page all along. The script now creates
NavTest.NavTest_Login and the profiles use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
mendixlabs#1570 resolved the login page like the home and not-found
pages. The sign-in page may be the theme's static login.html rather than a
page document, so a login page the project does not have is not a dangling
reference, and mx check accepts it: the doctype navigation example names
Administration.login, which the test project does not have, and builds
clean. The check refused it.

The login page is no longer a target. A new test pairs a login page outside
the project (accepted) with a missing not-found page (still refused) as its
control, and the "login page" case leaves the every-kind table. The
navigation example goes back to main's version (undoing b43f1d7 and
2f9876b), so it now also covers a login page outside the project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
Integrate wave 1 of the upstream PRs (mendixlabs/mxcli mendixlabs#1574 mendixlabs#1575 mendixlabs#1572 mendixlabs#1570 mendixlabs#1563 mendixlabs#1561 mendixlabs#1568 mendixlabs#1566 #1368), with review follow-ups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
@ako
ako merged commit 55a3b50 into mendixlabs:main Oct 20, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants