Skip to content

feat: mxcli marketplace package exports a module as a .mpk (closes #1382) - #1383

Open
stagknee wants to merge 2 commits into
mendixlabs:mainfrom
stagknee:feat/module-package-export
Open

stagknee wants to merge 2 commits into
mendixlabs:mainfrom
stagknee:feat/module-package-export

Conversation

@stagknee

Copy link
Copy Markdown
Contributor

Closes #1382.

What it does

mxcli marketplace package <Module> -p app.mpr [-o dir] [--all-libs] [--exclude <regex>] [--exclude-managed-dependencies] [--mxbuild-path p] [--json] wraps mx create-module-package. It is the reverse of marketplace install --file.

  • Arguments: create-module-package [-l unless --all-libs] [-e <regex>] [-m] -d <staging> <copy.mpr> <Module>. -e takes a single regex, so alternatives are joined with |; the help says so, and that the separator must be written as ..
  • mx is resolved with docker.ResolveMxForVersion for the project's version, with PrepareMxCommand applied.
  • The project is untouched. A raw mx create-module-package rewrote theme-cache/…/theme.compiled.css(.map), added deployment/sass/main.scss and created themesource/<module>/ in the project. So it runs on a temporary copy, through a new docker.WithProjectCopy wrapper over the existing copyProjectToTemp that docker.Check uses. Measured: a recursive listing of the app (1104 files, sizes and mtimes) was identical before and after a default run and an --all-libs run.
  • The module is checked case-insensitively against the project. A miss lists the modules and doesn't run mx.
  • mx exit codes 1–4 get messages saying what to change, plus mx's output with temp paths mapped back. "Exit 0 but no package" is an error.
  • Staging: mx writes into a fresh staging folder inside -o, and the package is moved to <out>/<Module>.mpk. A stale package is never reported as the new export.
  • After the export, the zip is opened to report path, size and file count. A warning on stderr lists any other module's resources/ (and, defensively, javasource/ and themesource/) entries, with a plain-dot --exclude suggestion. A folder whose pattern would also match the module's own files is reported as not safely excludable.
  • --json prints {path, sizeBytes, files, foreignEntries}, with foreignEntries always an array. Warnings stay on stderr.
  • Docs: a new section in docs-site/src/guides/marketplace.md, a row in reference/capabilities.md, a section in the download-marketplace-content skill, and a CHANGELOG entry under Unreleased → Added.

Evidence

  • Tests (cmd_marketplace_package_test.go, 13, with mx and the project reader stubbed): argument mapping, exit-code mapping (1/2/3/4/other), unknown module, exit 0 without a package, foreign-entry warning, JSON shape, replacing an older package, stale package ignored, classification on a synthetic zip, the plain-dot pattern, and the ambiguity guard. TestWithProjectCopy_WritesStayOnTheCopy covers the copy. Against a stub runPackage, all failed (for example "exit 3: error … does not mention "module name"").
  • Real mx (Studio Pro 11.12.5) on a scratch app:
    • Default run: 11 files, with 3 foreign resources/ entries, warned.
    • --all-libs: 12 files.
    • --exclude '^resources.EventLog.|…': 8 files, no warning.
    • Wrong module: exit 1 with the module list.
    • Bad regex: mx's exit 1 is surfaced, and the output dir is left empty.
  • Round trip: the exported package was installed into a second fresh app with mxcli marketplace install --file, followed by the install's own fix widgets / fix design-properties step. mx check then gave 0 errors, and the module's entity and microflow describe correctly there.
  • Windows (Go 1.26.6), ./cmd/mxcli and ./cmd/mxcli/docker: an identical failing list before and after (pre-existing Windows failures). go vet is clean on windows, linux and darwin.
  • Linux (golang:1.26.6-bookworm, ANTLR 4.13.2, the same as CI): make grammar build test check-mdl check-findings check-conformance check-migration-reference lint-go all pass.

Not covered

  • Studio Pro actually holding the project open. A .mpr.lock beside the project didn't stop the export, which reads only.
  • --exclude isn't validated in Go (.NET regex); mx's rejection is what the user sees.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

AI Code Review

Summary

This PR implements the mxcli marketplace package command to export a module as a .mpk file (reverse of marketplace install --file). It wraps Mendix's mx create-module-package with proper error handling, temporary project copying, and user feedback.

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

  • In cmd_marketplace_package_test.go, the test TestPackage_WarnsAboutForeignEntries uses a hardcoded warning message check that might be fragile if the exact wording changes. However, this is acceptable as it's testing the specific behavior.
  • Some test helper functions could use more descriptive names (e.g., pkgArgAfter is clear but pkgContainsArg might be better as containsArg).

What Looks Good

  • Thorough implementation: Complete command with all specified flags and behaviors
  • Excellent test coverage: 377-line test file covering argument mapping, exit codes, edge cases, and isolation
  • Proper temporary copying: Uses WithProjectCopy to ensure original project remains unmodified (verified by tests)
  • Comprehensive error handling: Maps mx exit codes 1-4 to meaningful messages, handles "exit 0 but no package" as error
  • User-friendly warnings: Identifies foreign module entries in resources/javasource/themesource and suggests --exclude patterns
  • JSON support: Properly structured JSON output with foreignEntries as array (never null)
  • Documentation: All relevant docs updated (guides, skills, changelog, capabilities)
  • Isolation verified: Tests confirm staging directories are cleaned up and original project untouched
  • Real-world validation: Evidence shows testing with actual mx (Studio Pro 11.12.5) and round-trip validation

Recommendation

Approve - The PR fully implements the requested feature with high quality, thorough testing, proper documentation, and follows all project conventions. No blocking issues identified.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

This was referenced Oct 12, 2026
@ako

ako commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Built the PR head, ran go vet and the PR's tests (all pass), and test-merged it with current main (clean). I could not try a real export — no mxbuild available — so mx itself was only exercised through the test seams.

Solid work overall: running mx on a project copy, exporting into a fresh staging folder (so a stale .mpk is never reported as this run's, and "exit 0, no package" is caught), the self-check that keeps the suggested --exclude from matching the module's own files, the module list on an unknown name, and the mapped exit codes.

Should fix before merge
The resolved mx is never checked against the project's Mendix version. docker.ResolveMxForVersion prefers any mx on PATH over the version-specific one, and falls back to any cached mxbuild when the right version is missing. marketplace/scratch.go:150-163 hit exactly this ("asking for 11.6.6 on a machine holding 11.12.1 returns 11.12.1 without complaint") and refuses a mismatch. Here the package would silently be made by a different Mendix version than the app it came from, which may only surface when it is installed into an app on the original version. The progress line names the mx path, but --json discards progress, so a CI caller sees nothing.

Suggest verifying the version and refusing on a mismatch, as scratch.go does (an explicit --mxbuild-path as the override), or at least warning on stderr.

Minor
inspectPackage is only called from its own test; runPackage calls readPackageEntries / classifyPackageEntries directly. Use it or drop it.
On exit 0, mx's output is discarded, so any warnings it prints never reach the user. Consider sending it to stderr.
ctx is checked once, before the copy, but never given to the child; exec.CommandContext would let cancellation stop mx.
The printed --exclude '|' breaks when pasted if the user's own pattern contains a '. The generated part is safe.
With -o inside the project (e.g. ./dist), earlier packages there are copied along with the project. Harmless, just slower.
The skill change is in .claude/skills/mendix/, which is where skills are meant to be edited, and the CHANGELOG and docs-site entries are present.

stagknee and others added 2 commits October 15, 2026 12:21
…ndixlabs#1382)

Wraps `mx create-module-package` (-l by default, --all-libs to bundle every JAR),
the reverse of `marketplace install --file`. Warns on stderr about other modules'
resources/javasource/themesource files in the package, with the plain-dot --exclude
that drops them. mx writes into the project (measured), so it runs on a temp copy.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… review follow-ups (mendixlabs#1383)

ResolveMxForVersion prefers an mx on PATH and falls back to any cached one, so
the package could silently be made by another Mendix version than its app.
mxcli now reads the version of the mx it found (mx.deps.json beside it, else
its version-named folder) and refuses a different major.minor.patch, or an mx
whose version it cannot tell, naming both versions and the fix. --mxbuild-path
is the override: a mismatch then only warns on stderr; a path that does not
exist is an error rather than a quiet fall back to PATH.

Also: mx's output on a successful export goes to stderr; the context reaches
mx (exec.CommandContext); the printed --exclude is quoted for a POSIX shell;
an output directory inside the project is not copied into the temporary copy;
the unused inspectPackage is dropped.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@stagknee
stagknee force-pushed the feat/module-package-export branch from 4099f0c to ac909ae Compare October 15, 2026 19:37
@stagknee

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Everything is addressed in a second commit, fix: marketplace package refuses an mx of another Mendix version, and review follow-ups, and the branch is rebased on current main.

Should fix: the resolved mx is now checked against the project's Mendix version.

  • docker.MxBinaryVersion(mxPath) (new, in cmd/mxcli/docker/mx_version.go) reads the version from the mx.deps.json that ships next to every mx. That file has a "mx/11.12.5" key, which I checked on 10.24.9 and 11.12.5. If the file is missing, it falls back to the version-named folder (…/mxbuild/<ver>/modeler/mx or …/Mendix/<ver>/modeler/mx), following symlinks first.

  • Without --mxbuild-path, a mismatch or an unknown version is refused.

    • The error names both versions.
    • It says to run mxcli setup mxbuild --version X.
    • It points out that an mx on PATH wins over a cached one, and mentions --mxbuild-path.

    Nothing is written in that case.

  • With an explicit --mxbuild-path, the same finding is a warning on stderr. That flag is the deliberate override. --json stdout stays clean, and the progress line now also shows the mx version.

  • The comparison is major.minor.patch, so a build-numbered 10.24.9.81004 matches a 10.24.9 project. That is slightly looser than scratch.go's exact string. Say if you'd rather have exact equality.

  • One addition beyond the review: an explicit --mxbuild-path must exist. ResolveMxForVersion falls back to PATH when that path is bad, which would have turned a typo into an unchecked export. I didn't change ResolveMxForVersion itself, because other commands share it.

Minor:

  1. inspectPackage is removed. Its test is now TestClassifyPackageEntries_ForeignEntries on readPackageEntries + classifyPackageEntries, with the same coverage.
  2. mx's output on a successful run now goes to stderr, so its "module uses other modules" warnings reach the user. It isn't repeated in the "no .mpk" error.
  3. The mx call uses exec.CommandContext, so cancellation stops mx. A test runs the real runner against a sleeping child and checks that the deadline stops it.
  4. The printed --exclude is quoted for POSIX shells (' becomes '\''). It's tested with it's and 'a'|'b', and the docs note to quote for your own shell, since PowerShell differs.
  5. An -o folder inside the project is now left out of the temp copy. The copy helpers take optional skip dirs matched with os.SameFile, so existing callers are unchanged. The project folder itself is never skipped (-o . still copies it), and both cases are tested.

Evidence:

  • The tests were written first and failed to build against the old code (the new seams didn't exist). Afterwards they all pass:

    • go test ./cmd/mxcli/ -run 'Package|Marketplace', including 11 new tests;
    • the new MxBinaryVersion, SameMendixRelease and copy-skip tests in cmd/mxcli/docker.

    The other failures in cmd/mxcli/docker on Windows are the same 12 as on the previous commit (symlink privilege, local Studio Pro installs).

  • End to end, with real mx from Studio Pro on a scratch copy of an 11.12.5 app:

    Case Result
    default resolution (mx 11.12.5) exported the module (32 files); mx's warnings now on stderr
    an 11.12.1 mx first on PATH refused; error names 11.12.1 vs 11.12.5 and the fix; no output
    the same 11.12.1 via --mxbuild-path … --json exported, with the warning on stderr and clean JSON on stdout
    a nonexistent --mxbuild-path clear error
    -o dist inside the project, run twice both succeed; nothing left in the project
  • Linux (golang:1.26.6-bookworm, ANTLR 4.13.2): make grammar build test check-mdl check-findings check-conformance check-migration-reference lint-go all pass.

@github-actions

Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

None found.

What Looks Good

  • Excellent test coverage: The PR includes 624 lines of comprehensive unit tests covering argument mapping, exit-code handling, unknown modules, foreign-entry warnings, JSON output, and copy functionality. Real mx validation on a scratch app was also performed.
  • Proper isolation: The command runs mx create-module-package on a temporary copy of the project, leaving the original untouched (verified by identical before/after file listings).
  • User-friendly error handling: mx exit codes 1-4 are mapped to meaningful messages with mx's output included; unknown modules list available modules instead of failing with exit code 3.
  • Helpful foreign entry detection: After export, the package is scanned for resources/javasource/themesource from other modules, with warnings and actionable --exclude suggestions on stderr.
  • Thorough documentation: Updates to marketplace guide, capabilities reference, download-marketplace-content skill, and changelog.
  • Robust temporary copy handling: Enhanced WithProjectCopy and copyProjectTree functions properly skip specified folders (like output directories) while maintaining symlink safety.
  • Correct version validation: Uses docker.ResolveMxForVersion and checks mx version against project version via mx.deps.json or folder name, refusing mismatches unless --mxbuild-path is used.
  • Flag mapping accuracy: All command-line flags correctly map to mx options (-l, -e, -m, etc.) with proper handling of defaults and overrides.
  • JSON output compliance: When --json is used, outputs clean {path, sizeBytes, files, foreignEntries} with foreignEntries always an array.

Recommendation

Approve. This PR implements a well-tested, isolated feature that follows project patterns, provides excellent user experience through clear errors and warnings, and maintains the integrity of the original project. The functionality is the logical counterpart to marketplace install --file and addresses a clear user need for exporting modules as .mpk packages.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

ako added a commit that referenced this pull request Oct 15, 2026
…5e8da

docs(run): the runtime does not derive a root URL from the bind address

This branch has not been deployed

No deployments
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.

Feature: mxcli marketplace package <Module> — export a module as a .mpk (the reverse of install --file)

2 participants