mxrb 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mxrb +458 -10
  3. data/docs/de-DE/README.md +1 -0
  4. data/docs/de-DE/oql-sql.md +42 -0
  5. data/docs/de-DE/project-structure.md +201 -2
  6. data/docs/de-DE/runtime-ruby.md +104 -0
  7. data/docs/de-DE/runtime-schema-migrations.md +44 -0
  8. data/docs/de-DE/validation-matrix.md +42 -4
  9. data/docs/en-US/README.md +1 -0
  10. data/docs/en-US/oql-sql.md +41 -0
  11. data/docs/en-US/project-structure.md +192 -2
  12. data/docs/en-US/runtime-ruby.md +100 -0
  13. data/docs/en-US/runtime-schema-migrations.md +41 -0
  14. data/docs/en-US/validation-matrix.md +39 -4
  15. data/docs/pt-BR/README.md +1 -0
  16. data/docs/pt-BR/entity-dsl.md +8 -0
  17. data/docs/pt-BR/oql-sql.md +41 -0
  18. data/docs/pt-BR/project-structure.md +197 -2
  19. data/docs/pt-BR/runtime-ruby.md +108 -0
  20. data/docs/pt-BR/runtime-schema-migrations.md +40 -0
  21. data/docs/pt-BR/validation-matrix.md +42 -5
  22. data/docs/pt-BR/writing.md +28 -0
  23. data/lib/mxrb/cli/help.rb +426 -0
  24. data/lib/mxrb/cli/release.rb +137 -0
  25. data/lib/mxrb/compiler/compatibility_analyzer.rb +1 -1
  26. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +8 -1
  27. data/lib/mxrb/compiler/domain_document_compiler.rb +3 -0
  28. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +11 -1
  29. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +76 -19
  30. data/lib/mxrb/compiler/legacy_custom_widget_compiler.rb +159 -0
  31. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +43 -7
  32. data/lib/mxrb/compiler/legacy_page_builder.rb +1248 -23
  33. data/lib/mxrb/compiler/packager.rb +27 -13
  34. data/lib/mxrb/compiler/page_bundle_compiler.rb +13 -4
  35. data/lib/mxrb/compiler/portable_packager.rb +16 -11
  36. data/lib/mxrb/compiler/web_bundle_builder.rb +5 -1
  37. data/lib/mxrb/compiler/web_shell_materializer.rb +42 -2
  38. data/lib/mxrb/compiler/widget_package_extractor.rb +3 -0
  39. data/lib/mxrb/domain_diagram/lifecycle.rb +272 -0
  40. data/lib/mxrb/domain_diagram.rb +409 -0
  41. data/lib/mxrb/dsl/builder.rb +62 -11
  42. data/lib/mxrb/environment.rb +226 -0
  43. data/lib/mxrb/exporter.rb +265 -45
  44. data/lib/mxrb/http/server.rb +107 -0
  45. data/lib/mxrb/initializer.rb +80 -9
  46. data/lib/mxrb/integrity/validator.rb +32 -7
  47. data/lib/mxrb/io/bson_codec.rb +42 -1
  48. data/lib/mxrb/io/mpr_file.rb +146 -3
  49. data/lib/mxrb/model/attribute.rb +3 -0
  50. data/lib/mxrb/model/entity.rb +44 -2
  51. data/lib/mxrb/model/microflow.rb +3 -1
  52. data/lib/mxrb/model/module.rb +71 -0
  53. data/lib/mxrb/model/page.rb +126 -15
  54. data/lib/mxrb/model/project.rb +3 -0
  55. data/lib/mxrb/modeler/catalog.rb +178 -0
  56. data/lib/mxrb/official_marketplace/module_package_importer.rb +23 -12
  57. data/lib/mxrb/official_marketplace/widget_package_installer.rb +15 -11
  58. data/lib/mxrb/official_marketplace.rb +9 -3
  59. data/lib/mxrb/oql/reverse_translator.rb +394 -0
  60. data/lib/mxrb/oql/server.rb +4 -8
  61. data/lib/mxrb/oql.rb +32 -4
  62. data/lib/mxrb/progress.rb +242 -0
  63. data/lib/mxrb/project_lifecycle.rb +5 -2
  64. data/lib/mxrb/ruby_app/exporter.rb +1749 -0
  65. data/lib/mxrb/ruby_app/preset.rb +506 -0
  66. data/lib/mxrb/ruby_app/session_manager.rb +122 -0
  67. data/lib/mxrb/ruby_app.rb +1361 -0
  68. data/lib/mxrb/runtime/access_control.rb +408 -0
  69. data/lib/mxrb/runtime/native.rb +1015 -93
  70. data/lib/mxrb/runtime/scheduler.rb +396 -0
  71. data/lib/mxrb/runtime/schema_migrator.rb +598 -0
  72. data/lib/mxrb/runtime/shared_store.rb +335 -0
  73. data/lib/mxrb/runtime/sqlite_store.rb +729 -0
  74. data/lib/mxrb/scaffold/generator.rb +11 -3
  75. data/lib/mxrb/team_server.rb +10 -7
  76. data/lib/mxrb/uml/activity_diagram.rb +118 -0
  77. data/lib/mxrb/uml/class_diagram.rb +118 -0
  78. data/lib/mxrb/uml/sequence_diagram.rb +112 -0
  79. data/lib/mxrb/uml/server.rb +146 -0
  80. data/lib/mxrb/uml/support.rb +35 -0
  81. data/lib/mxrb/version.rb +1 -1
  82. data/lib/mxrb/web_ui/assets/abnfDiagram-N423BO3Z-C4JD3cEC.js +1 -0
  83. data/lib/mxrb/web_ui/assets/arc-3Z53kgFp.js +1 -0
  84. data/lib/mxrb/web_ui/assets/architecture-TIHT7OUA-BStBTLea.js +1 -0
  85. data/lib/mxrb/web_ui/assets/architectureDiagram-T3A2C74G-DBwqnM4J.js +36 -0
  86. data/lib/mxrb/web_ui/assets/array-BifhSqXX.js +1 -0
  87. data/lib/mxrb/web_ui/assets/blockDiagram-VBNYF7ZC-DLtuHt-j.js +132 -0
  88. data/lib/mxrb/web_ui/assets/c4Diagram-5PPSVZJV-ConSk_pe.js +10 -0
  89. data/lib/mxrb/web_ui/assets/channel-BhPvD7Dt.js +1 -0
  90. data/lib/mxrb/web_ui/assets/chunk-2GRJ4B5K-D2HpWsf_.js +1 -0
  91. data/lib/mxrb/web_ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  92. data/lib/mxrb/web_ui/assets/chunk-4I5QYGJK-CiIdNwaF.js +1 -0
  93. data/lib/mxrb/web_ui/assets/chunk-5RXB4S5H-Bu_AaEkE.js +231 -0
  94. data/lib/mxrb/web_ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  95. data/lib/mxrb/web_ui/assets/chunk-6Q2QTUOP-wQkNmt_X.js +88 -0
  96. data/lib/mxrb/web_ui/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
  97. data/lib/mxrb/web_ui/assets/chunk-GF5L2VYU-G8MmiBzU.js +206 -0
  98. data/lib/mxrb/web_ui/assets/chunk-I66GZJ75-CulcrLPh.js +127 -0
  99. data/lib/mxrb/web_ui/assets/chunk-JQJVKLGR-ukjzcdlL.js +156 -0
  100. data/lib/mxrb/web_ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  101. data/lib/mxrb/web_ui/assets/chunk-KBJHAD2P-Wm35lYov.js +1 -0
  102. data/lib/mxrb/web_ui/assets/chunk-KEIR6QF5-BICK3FdT.js +161 -0
  103. data/lib/mxrb/web_ui/assets/chunk-NSK5VX7P-D2nYrCY4.js +2 -0
  104. data/lib/mxrb/web_ui/assets/chunk-QR6OTTB3-BrdIzg4Q.js +62 -0
  105. data/lib/mxrb/web_ui/assets/chunk-RYQCIY6F-CCfZMcW6.js +1 -0
  106. data/lib/mxrb/web_ui/assets/chunk-UBXNYLIW-BPEVYsFK.js +1 -0
  107. data/lib/mxrb/web_ui/assets/chunk-W5SLKNZC-2E6OPhA4.js +1 -0
  108. data/lib/mxrb/web_ui/assets/chunk-WRU74C26-CBIvOyWp.js +70 -0
  109. data/lib/mxrb/web_ui/assets/chunk-XXDRQBXY-pH58XAyl.js +1 -0
  110. data/lib/mxrb/web_ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  111. data/lib/mxrb/web_ui/assets/classDiagram-JCYQIIEL-xiqo4D89.js +1 -0
  112. data/lib/mxrb/web_ui/assets/classDiagram-v2-OCEON4UE-xiqo4D89.js +1 -0
  113. data/lib/mxrb/web_ui/assets/cose-bilkent-JH36ORCC-t_flPpii.js +1 -0
  114. data/lib/mxrb/web_ui/assets/cynefin-VYW2F7L2-BIqktTEv.js +1 -0
  115. data/lib/mxrb/web_ui/assets/cynefinDiagram-MW4NZA55-C2d--lut.js +62 -0
  116. data/lib/mxrb/web_ui/assets/cytoscape.esm-B-NFISlW.js +321 -0
  117. data/lib/mxrb/web_ui/assets/dagre-Buvkdvvj.js +1 -0
  118. data/lib/mxrb/web_ui/assets/dagre-VZM6K2ZE-CSz2_tb5.js +4 -0
  119. data/lib/mxrb/web_ui/assets/defaultLocale-BFoDCU3G.js +1 -0
  120. data/lib/mxrb/web_ui/assets/diagram-7IWD3JNH-DCt7u7V_.js +30 -0
  121. data/lib/mxrb/web_ui/assets/diagram-B4RE2ZJO-DqZBqkMu.js +3 -0
  122. data/lib/mxrb/web_ui/assets/diagram-LBJQPF4R-ClIYbIsI.js +24 -0
  123. data/lib/mxrb/web_ui/assets/diagram-Q27KOJAE-DFxhvthJ.js +24 -0
  124. data/lib/mxrb/web_ui/assets/diagram-UB23O5K3-BSUTXptv.js +41 -0
  125. data/lib/mxrb/web_ui/assets/dist-D2qOEbeJ.js +1 -0
  126. data/lib/mxrb/web_ui/assets/domain-mGOZ1KCG.css +1 -0
  127. data/lib/mxrb/web_ui/assets/domain-qsjXhy4v.js +1 -0
  128. data/lib/mxrb/web_ui/assets/ebnfDiagram-BXEA7PRR-B5reICEi.js +1 -0
  129. data/lib/mxrb/web_ui/assets/erDiagram-JOGREHBK-Co5Mxn2p.js +85 -0
  130. data/lib/mxrb/web_ui/assets/eventmodeling-45OFAUF4-C1ylhSks.js +1 -0
  131. data/lib/mxrb/web_ui/assets/flowDiagram-UKHOOZJN-CHXaJIaS.js +1 -0
  132. data/lib/mxrb/web_ui/assets/ganttDiagram-PKOTCBZU-DPPPaWaz.js +292 -0
  133. data/lib/mxrb/web_ui/assets/gitGraph-TEB2WS4Q-qS77ywA9.js +1 -0
  134. data/lib/mxrb/web_ui/assets/gitGraphDiagram-DS77QQ5N-DpAv6-BT.js +106 -0
  135. data/lib/mxrb/web_ui/assets/graphlib-DS17s2tU.js +1 -0
  136. data/lib/mxrb/web_ui/assets/info-DKCQHKI2-C9VD1Y4e.js +1 -0
  137. data/lib/mxrb/web_ui/assets/infoDiagram-6WML65LV-Bk1kbcCy.js +2 -0
  138. data/lib/mxrb/web_ui/assets/init-C-OQMol4.js +1 -0
  139. data/lib/mxrb/web_ui/assets/ishikawaDiagram-WSZJBQD7-0gcISBFR.js +70 -0
  140. data/lib/mxrb/web_ui/assets/journeyDiagram-NVQOT4AX-Dt0RQnlh.js +139 -0
  141. data/lib/mxrb/web_ui/assets/jsx-runtime-DV0a5kSb.js +9 -0
  142. data/lib/mxrb/web_ui/assets/kanban-definition-27J2QSJJ-DIPQM7w9.js +89 -0
  143. data/lib/mxrb/web_ui/assets/katex-CXMH3UgJ.js +257 -0
  144. data/lib/mxrb/web_ui/assets/line-C0rYD-tL.js +1 -0
  145. data/lib/mxrb/web_ui/assets/linear-BPPxWorj.js +1 -0
  146. data/lib/mxrb/web_ui/assets/map-BaFkSB1l.js +1 -0
  147. data/lib/mxrb/web_ui/assets/mermaid-parser.core-B_UPzTxa.js +7 -0
  148. data/lib/mxrb/web_ui/assets/mindmap-definition-FAOFIHXS-CdJRSMuk.js +96 -0
  149. data/lib/mxrb/web_ui/assets/modeler-BNqHthwk.css +1 -0
  150. data/lib/mxrb/web_ui/assets/modeler-CQmqflbg.js +1 -0
  151. data/lib/mxrb/web_ui/assets/ordinal-BDEzSJ7C.js +1 -0
  152. data/lib/mxrb/web_ui/assets/packet-7NZHBO7P-CwQ4NSBs.js +1 -0
  153. data/lib/mxrb/web_ui/assets/path-fybaL0A-.js +1 -0
  154. data/lib/mxrb/web_ui/assets/pegDiagram-VL7TDLO6-D40S7ppx.js +1 -0
  155. data/lib/mxrb/web_ui/assets/pie-RZYD4A2V-CJroHMKh.js +1 -0
  156. data/lib/mxrb/web_ui/assets/pieDiagram-7S7Q4E2Y-QB54__BV.js +39 -0
  157. data/lib/mxrb/web_ui/assets/quadrantDiagram-CIZ2JOQS-DBhDS6ag.js +7 -0
  158. data/lib/mxrb/web_ui/assets/radar-I7S5WNFK-DdIUwd2d.js +1 -0
  159. data/lib/mxrb/web_ui/assets/railroad-3IZDKUUU-B1Lw6Xyq.js +1 -0
  160. data/lib/mxrb/web_ui/assets/railroad-abnf-AHOZXSZD-CQgQoeak.js +1 -0
  161. data/lib/mxrb/web_ui/assets/railroad-ebnf-EBAXGLYW-D7qEQ2x8.js +1 -0
  162. data/lib/mxrb/web_ui/assets/railroad-peg-LSFZ7HO6-C005Vxbb.js +1 -0
  163. data/lib/mxrb/web_ui/assets/railroadDiagram-AXF67PYL-BBP2qk7M.js +1 -0
  164. data/lib/mxrb/web_ui/assets/requirementDiagram-LRYGKXZP-D2cLsAoI.js +84 -0
  165. data/lib/mxrb/web_ui/assets/rough.esm-Dy-Kn_BL.js +1 -0
  166. data/lib/mxrb/web_ui/assets/sankeyDiagram-W5VNT64P-Bpi3FiP2.js +40 -0
  167. data/lib/mxrb/web_ui/assets/sequenceDiagram-SI44F4Z6-DCRcDlVe.js +162 -0
  168. data/lib/mxrb/web_ui/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
  169. data/lib/mxrb/web_ui/assets/src-CvxfUak2.js +1 -0
  170. data/lib/mxrb/web_ui/assets/stateDiagram-OKZ733FA-C2GCEoxl.js +1 -0
  171. data/lib/mxrb/web_ui/assets/stateDiagram-v2-UEYNNEHI-D2gWgedh.js +1 -0
  172. data/lib/mxrb/web_ui/assets/swimlanes-SLNWSIFB-CRI9irEW.js +2 -0
  173. data/lib/mxrb/web_ui/assets/swimlanesDiagram-ULZ7WXOC-C5Wb_-hZ.js +8 -0
  174. data/lib/mxrb/web_ui/assets/theme-_mxc7BdY.css +1 -0
  175. data/lib/mxrb/web_ui/assets/timeline-definition-Z64GVDOM-B1-mNg-h.js +120 -0
  176. data/lib/mxrb/web_ui/assets/treeView-QDETBFTQ-7CTvIwKq.js +1 -0
  177. data/lib/mxrb/web_ui/assets/treemap-6X3UGDF4-CtL22pq5.js +1 -0
  178. data/lib/mxrb/web_ui/assets/uml-C3KAFGTq.js +51 -0
  179. data/lib/mxrb/web_ui/assets/uml-Dn-qd01X.css +1 -0
  180. data/lib/mxrb/web_ui/assets/vennDiagram-T6HMQDX7-BzVnUEAy.js +34 -0
  181. data/lib/mxrb/web_ui/assets/wardley-OPB4EBWU-JNi-QNag.js +1 -0
  182. data/lib/mxrb/web_ui/assets/wardleyDiagram-T6FBY63Y-BUwmAQ-B.js +78 -0
  183. data/lib/mxrb/web_ui/assets/xychartDiagram-ELKLHX3M-DEQS8sKp.js +7 -0
  184. data/lib/mxrb/web_ui/domain.html +15 -0
  185. data/lib/mxrb/web_ui/modeler.html +16 -0
  186. data/lib/mxrb/web_ui/uml.html +31 -0
  187. data/lib/mxrb/web_ui.rb +89 -0
  188. data/lib/mxrb/widget_certification.rb +208 -0
  189. data/lib/mxrb/writer.rb +278 -41
  190. data/lib/mxrb.rb +38 -8
  191. metadata +157 -5
@@ -0,0 +1,426 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module CLI
5
+ # Central command catalog used by the welcome screen, --help and --commands.
6
+ # A single catalog keeps discovery text consistent with command dispatch.
7
+ # rubocop:disable Metrics
8
+ module Help
9
+ module_function
10
+
11
+ Command = Data.define(:name, :usage, :summary, :example)
12
+
13
+ COMMANDS = [
14
+ ['analyze', 'analyze FILE.mpr [--json]', 'Analyze native OQL and SQL query plans', 'mxrb analyze Shop.mpr'],
15
+ ['benchmark', 'benchmark FILE.mpr [--iterations N]', 'Measure model loading and validation performance',
16
+ 'mxrb benchmark Shop.mpr --iterations 5'],
17
+ ['cache', 'cache <status|warm|clear> FILE.mpr', 'Inspect or manage the semantic index cache',
18
+ 'mxrb cache status Shop.mpr'],
19
+ ['callees', 'callees FILE.mpr NAME', 'List artifacts called by a callable artifact',
20
+ 'mxrb callees Shop.mpr Sales.Submit'],
21
+ ['callers', 'callers FILE.mpr NAME', 'List callers of a callable artifact',
22
+ 'mxrb callers Shop.mpr Sales.Submit'],
23
+ ['changelog', 'changelog [VERSION]', 'Show release notes without updating MXRB', 'mxrb changelog'],
24
+ ['compare', 'compare LEFT.mpr RIGHT.mpr', 'Compare structural MPR snapshots',
25
+ 'mxrb compare before.mpr after.mpr'],
26
+ ['db', 'db <ACTION> FILE.mpr [options]', 'Manage the isolated PostgreSQL and Runtime workspace',
27
+ 'mxrb db status Shop.mpr'],
28
+ ['design', 'design <init|scan|migrate> ...', 'Create or migrate the project design system',
29
+ 'mxrb design scan Shop.mpr'],
30
+ ['describe', 'describe FILE.mpr NAME', 'Show incoming and outgoing artifact references',
31
+ 'mxrb describe Shop.mpr Sales.Order'],
32
+ ['diff', 'diff LEFT.mpr RIGHT.mpr', 'Print typed semantic model changes', 'mxrb diff before.mpr after.mpr'],
33
+ ['doctor', 'doctor [DIR] [--json]', 'Check project files and the local toolchain', 'mxrb doctor .'],
34
+ ['diagram-er', 'diagram-er FILE.mpr [options]',
35
+ 'Edit a DBeaver-style browser ER diagram and export PNG',
36
+ 'mxrb diagram-er Shop.mpr --module Sales'],
37
+ ['dump-unit', 'dump-unit FILE.mpr UNIT_ID', 'Hex-dump a raw MPR unit', 'mxrb dump-unit Shop.mpr UNIT_ID'],
38
+ ['env', 'env [DIR] [--environment NAME] [--json]', 'Inspect an environment profile without values',
39
+ 'mxrb env . --environment qa'],
40
+ ['evaluate', 'evaluate FILE.mpr EVALUATION.rb', 'Run Ruby model evaluations',
41
+ 'mxrb evaluate Shop.mpr checks.rb'],
42
+ ['export', 'export FILE.mpr DIR [--mode mendix|ruby] [stack]',
43
+ 'Export an MPR as editable Mendix DSL or a Ruby app',
44
+ 'mxrb export Shop.mpr shop-ruby --mode ruby'],
45
+ ['find', 'find FILE.mpr TEXT [--semantic]', 'Find artifacts by name or semantic text',
46
+ 'mxrb find Shop.mpr Order'],
47
+ ['frontend', 'frontend migrate FILE.mpr [--apply] [--json]', 'Inspect or apply frontend schema migrations',
48
+ 'mxrb frontend migrate Shop.mpr'],
49
+ ['functional-instrument', 'functional-instrument FILE.mpr TEST.rb', 'Instrument an MPR for functional testing',
50
+ 'mxrb functional-instrument Shop.mpr smoke.rb'],
51
+ ['generate', 'generate DEFINITION.rb [FILE.mpr]', 'Generate an MPR from Ruby DSL',
52
+ 'mxrb generate project.rb build/Shop.mpr'],
53
+ ['impact', 'impact FILE.mpr NAME', 'Find transitive dependents of an artifact',
54
+ 'mxrb impact Shop.mpr Sales.Order'],
55
+ ['init', 'init NAME [--mode mendix|ruby] [--mxrb-path DIR]',
56
+ 'Create a new editable Mendix DSL or conventional Ruby application',
57
+ 'mxrb init MyApp --mode ruby'],
58
+ ['inspect', 'inspect FILE.mpr', 'Explore every raw unit in an MPR', 'mxrb inspect Shop.mpr'],
59
+ ['lint', 'lint FILE.mpr', 'Run semantic diagnostics', 'mxrb lint Shop.mpr'],
60
+ ['marketplace', 'marketplace <ACTION> [options]', 'Work with official and GitHub marketplace packages',
61
+ 'mxrb marketplace search charts'],
62
+ ['mda', 'mda <inspect|compare> ...', 'Inspect or compare Mendix deployment archives',
63
+ 'mxrb mda inspect Shop.mda'],
64
+ ['migrate', 'migrate <check|plan> [DIR] [--json]', 'Compare project.rb output with the current MPR',
65
+ 'mxrb migrate check .'],
66
+ ['module', 'module <new|search|add> ...', 'Create or install Ruby modules', 'mxrb module new Billing'],
67
+ ['modules', 'modules FILE.mpr', 'List modules in an MPR', 'mxrb modules Shop.mpr'],
68
+ ['move', 'move FILE.mpr NAME CONTAINER [--apply]', 'Preview or apply a same-module unit move',
69
+ 'mxrb move Shop.mpr Sales.Flow Sales.Folder --apply'],
70
+ ['oql', 'oql FILE.mpr [--dialect DIALECT] [--json]', 'Show native OQL and its read-only SQL projection',
71
+ 'mxrb oql Shop.mpr --dialect postgresql'],
72
+ ['pack', 'pack FILE.mpr --output FILE.mda [--force]', 'Build an MDA without mx or mxbuild',
73
+ 'mxrb pack Shop.mpr --output Shop.mda'],
74
+ ['portable', 'portable FILE.mpr --output runtime.zip', 'Build a portable executable Runtime ZIP',
75
+ 'mxrb portable Shop.mpr --output Shop.zip'],
76
+ ['preflight', 'preflight FILE.mpr [--json]', 'Audit compiler and Runtime compatibility',
77
+ 'mxrb preflight Shop.mpr'],
78
+ ['project', 'project inspect [DIR] [--json]', 'Inspect an MXRB project workspace', 'mxrb project inspect .'],
79
+ ['protocols', 'protocols FILE.mpr [--json]', 'Audit known protocol connector modules',
80
+ 'mxrb protocols Shop.mpr'],
81
+ ['refs', 'refs FILE.mpr NAME', 'Find direct references to an artifact', 'mxrb refs Shop.mpr Sales.Order'],
82
+ ['remove', 'remove FILE.mpr NAME [--apply]', 'Preview or apply a reference-safe removal',
83
+ 'mxrb remove Shop.mpr Sales.Legacy --apply'],
84
+ ['rename', 'rename FILE.mpr OLD NEW [--apply]', 'Preview or apply a model-wide rename',
85
+ 'mxrb rename Shop.mpr Sales.Old Sales.New --apply'],
86
+ ['report', 'report FILE.mpr', 'Print coupling and reference summaries', 'mxrb report Shop.mpr'],
87
+ ['run', 'run [DIR] [options]', 'Start the Ruby backend and React client together',
88
+ 'mxrb run . --server-port 9292 --client-port 5173'],
89
+ ['scaffold', 'scaffold <list|destroy> ...', 'Inspect or remove registered scaffolds', 'mxrb scaffold list'],
90
+ ['search', 'search TEXT FILE.mpr [options]', 'Run ranked semantic search', 'mxrb search checkout Shop.mpr'],
91
+ ['serve', 'serve FILE.mpr [--port PORT]', 'Serve read-only SQL/OQL over loopback HTTP',
92
+ 'mxrb serve Shop.mpr --port 4567'],
93
+ ['sql', 'sql FILE.mpr "SELECT ..."', 'Run raw SQL against the MPR SQLite database',
94
+ 'mxrb sql Shop.mpr "SELECT * FROM Unit"'],
95
+ ['query', 'query "SELECT ..." --from sql|oql [options]',
96
+ 'Convert safe read-only queries between SQL and OQL',
97
+ 'mxrb query "SELECT p.name FROM shop$product p" --from sql --to oql'],
98
+ ['team-server', 'team-server <ACTION> [options]', 'Work with Mendix Team Server repositories',
99
+ 'mxrb team-server status ./app'],
100
+ ['test', 'test FILE.mpr TEST.rb [options]', 'Run isolated functional Runtime tests',
101
+ 'mxrb test Shop.mpr smoke.rb --native'],
102
+ ['tree', 'tree FILE.mpr [MODULE]', 'Browse the semantic project tree', 'mxrb tree Shop.mpr Sales'],
103
+ ['uml', 'uml FILE.mpr [options]',
104
+ 'View the project modeler or export class, activity, and sequence UML diagrams',
105
+ 'mxrb uml Shop.mpr --export class'],
106
+ ['update', 'update [--check|--changelog]', 'Check or install the latest published MXRB gem',
107
+ 'mxrb update --check'],
108
+ ['upgrade', 'upgrade --mendix VERSION [--target DIR] [--apply]', 'Transition the Mendix version in project.rb',
109
+ 'mxrb upgrade --mendix 11.12.1 --apply'],
110
+ ['validate', 'validate FILE.mpr', 'Validate MPR structure, hashes and contents', 'mxrb validate Shop.mpr'],
111
+ ['widgets', 'widgets sync DEFINITION.rb FILE.mpr', 'Synchronize pluggable widget schemas',
112
+ 'mxrb widgets sync project.rb Shop.mpr']
113
+ ].to_h { |values| [values.first, Command.new(*values)] }.freeze
114
+
115
+ SUBCOMMANDS = {
116
+ 'cache' => %w[status warm clear],
117
+ 'db' => %w[up sync down destroy status credentials url sql explain workload indexes shell],
118
+ 'design' => %w[init scan migrate],
119
+ 'diagram-er' => %w[up down status destroy],
120
+ 'frontend' => %w[migrate],
121
+ 'marketplace' => %w[login search show versions pull import audit list verify dependencies update remove],
122
+ 'mda' => %w[inspect compare],
123
+ 'migrate' => %w[check plan],
124
+ 'module' => %w[new search add],
125
+ 'project' => %w[inspect],
126
+ 'scaffold' => %w[list destroy],
127
+ 'team-server' => %w[login projects info branches commits clone status fetch pull push],
128
+ 'widgets' => %w[sync]
129
+ }.freeze
130
+
131
+ OPTIONS = {
132
+ 'init' => [
133
+ ['--mode MODE', 'Project structure: mendix (default) or ruby'],
134
+ ['--flymetothemoon', 'Sinatra/Puma/Rake/RSpec/ActiveRecord preset'],
135
+ ['--onrails', 'Rails/Puma/RSpec/ActiveRecord preset'],
136
+ ['--mxrb-path DIR', 'Use a local MXRB checkout in the Gemfile']
137
+ ],
138
+ 'export' => [
139
+ ['--mode MODE', 'Project structure: mendix (default) or ruby'],
140
+ ['--flymetothemoon', 'Apply the full Sinatra-based Ruby preset'],
141
+ ['--onrails', 'Apply the conventional Rails preset']
142
+ ],
143
+ 'diagram-er' => [
144
+ ['--module NAME', 'Show one module; repeat to show several'],
145
+ ['--output FILE', 'MPR copy that receives saved visual layout'],
146
+ ['--port PORT', 'Loopback browser editor port (default: 4568)'],
147
+ ['--force', 'Replace an existing output MPR copy']
148
+ ],
149
+ 'query' => [
150
+ ['--from LANGUAGE', 'Required source language: sql or oql'],
151
+ ['--to LANGUAGE', 'Target language; defaults to the opposite language'],
152
+ ['--dialect DIALECT', 'SQL dialect: ansi, postgresql or sql_server'],
153
+ ['--project FILE.mpr', 'Use the MPR to restore canonical entity and attribute names'],
154
+ ['--input FILE', 'Read the query from a file; use - for stdin'],
155
+ ['--json', 'Render the conversion result as JSON']
156
+ ],
157
+ 'uml' => [
158
+ ['--export TYPE', 'Print class, activity, or sequence diagram text'],
159
+ ['--format FORMAT', 'Export format: mermaid (default) or plantuml'],
160
+ ['--module NAME', 'Filter class diagram or select sequence module mode'],
161
+ ['--microflow NAME', 'Microflow for an activity diagram'],
162
+ ['--root NAME', 'Root microflow for a sequence call chain'],
163
+ ['--depth N', 'Sequence expansion depth (default: 2)'],
164
+ ['--port PORT', 'Loopback viewer port (default: 4569)']
165
+ ],
166
+ 'run' => [
167
+ ['--host HOST', 'Bind host (default: 127.0.0.1)'],
168
+ ['--server-port PORT', 'Ruby backend port (default: 9292)'],
169
+ ['--client-port PORT', 'React + Vite port (default: 5173)'],
170
+ ['--environment NAME', 'Load .env plus config/environments/NAME.env'],
171
+ ['--no-frontend', 'Start only the Ruby backend']
172
+ ],
173
+ 'env' => [
174
+ ['--environment NAME', 'Profile: development, qa, staging, production, or a safe custom name'],
175
+ ['--json', 'Print profile metadata and key names; values remain hidden']
176
+ ],
177
+ 'test' => [
178
+ ['--environment NAME', 'Apply the selected profile while the test runtime executes']
179
+ ]
180
+ }.freeze
181
+
182
+ NOTES = {
183
+ 'query' => [
184
+ '`--project` applies only when converting SQL to OQL.',
185
+ 'Unsafe or unsupported constructs are reported instead of approximated.'
186
+ ],
187
+ 'run' => [
188
+ 'Compatibility aliases: --api-port for --server-port, --port for --client-port.',
189
+ 'The global --no-progress option is optional and only suppresses progress output.'
190
+ ]
191
+ }.freeze
192
+
193
+ SUBCOMMAND_HELP = {
194
+ 'cache status' => ['cache status FILE.mpr [--json]', 'Inspect semantic cache health and size',
195
+ 'mxrb cache status Shop.mpr'],
196
+ 'cache warm' => ['cache warm FILE.mpr [--json]', 'Build or refresh the semantic cache',
197
+ 'mxrb cache warm Shop.mpr'],
198
+ 'cache clear' => ['cache clear FILE.mpr [--json]', 'Remove semantic cache entries',
199
+ 'mxrb cache clear Shop.mpr'],
200
+ 'diagram-er up' => ['diagram-er up FILE.mpr [options]', 'Start the ER editor in the background',
201
+ 'mxrb diagram-er up Shop.mpr --module Sales'],
202
+ 'diagram-er down' => ['diagram-er down FILE.mpr', 'Stop the ER editor and preserve its layout copy',
203
+ 'mxrb diagram-er down Shop.mpr'],
204
+ 'diagram-er status' => ['diagram-er status FILE.mpr [--json]', 'Show managed ER editor state',
205
+ 'mxrb diagram-er status Shop.mpr'],
206
+ 'diagram-er destroy' => ['diagram-er destroy FILE.mpr --yes',
207
+ 'Stop the ER editor and remove only its managed files',
208
+ 'mxrb diagram-er destroy Shop.mpr --yes'],
209
+ 'db up' => ['db up FILE.mpr [--port PORT]', 'Build, synchronize and start PostgreSQL and Runtime',
210
+ 'mxrb db up Shop.mpr'],
211
+ 'db sync' => ['db sync FILE.mpr [--port PORT]', 'Rebuild and synchronize the retained database',
212
+ 'mxrb db sync Shop.mpr'],
213
+ 'db down' => ['db down FILE.mpr', 'Stop containers while preserving data', 'mxrb db down Shop.mpr'],
214
+ 'db destroy' => ['db destroy FILE.mpr --yes', 'Remove the isolated containers, data and state',
215
+ 'mxrb db destroy Shop.mpr --yes'],
216
+ 'db status' => ['db status FILE.mpr', 'Show database and Runtime status', 'mxrb db status Shop.mpr'],
217
+ 'db credentials' => ['db credentials FILE.mpr [--copy]', 'Show or copy Runtime credentials',
218
+ 'mxrb db credentials Shop.mpr'],
219
+ 'db url' => ['db url FILE.mpr [--port PORT]', 'Print the read-only PostgreSQL URL', 'mxrb db url Shop.mpr'],
220
+ 'db sql' => ['db sql FILE.mpr "SELECT ..." [--write]', 'Execute SQL against the managed database',
221
+ 'mxrb db sql Shop.mpr "SELECT 1"'],
222
+ 'db explain' => ['db explain FILE.mpr "SELECT ..." [--analyze]',
223
+ 'Inspect a PostgreSQL or SQL Server query plan',
224
+ 'mxrb db explain Shop.mpr "SELECT * FROM orders"'],
225
+ 'db workload' => ['db workload FILE.mpr [--limit N]', 'Rank workload and schema pressure',
226
+ 'mxrb db workload Shop.mpr --limit 20'],
227
+ 'db indexes' => ['db indexes FILE.mpr [--limit N]', 'Advise evidence-backed indexes',
228
+ 'mxrb db indexes Shop.mpr'],
229
+ 'db shell' => ['db shell FILE.mpr [--write]', 'Open psql for the managed database', 'mxrb db shell Shop.mpr'],
230
+ 'design init' => ['design init [--target DIR]', 'Initialize design-system Ruby files', 'mxrb design init'],
231
+ 'design scan' => ['design scan FILE.mpr [--json]', 'Inventory CSS and SCSS design tokens',
232
+ 'mxrb design scan Shop.mpr'],
233
+ 'design migrate' => ['design migrate FILE.mpr LITERAL TOKEN [--apply]',
234
+ 'Preview or apply a literal-to-token migration',
235
+ 'mxrb design migrate Shop.mpr "#fff" color.surface --apply'],
236
+ 'frontend migrate' => ['frontend migrate FILE.mpr [--apply] [--json]',
237
+ 'Preview a frontend migration; the MPR is changed only when --apply is provided',
238
+ 'mxrb frontend migrate Shop.mpr --apply'],
239
+ 'marketplace login' => [
240
+ 'marketplace login (--pat-file FILE | --store-pat) [--no-verify]',
241
+ 'Configure a PAT file reference or explicit managed storage; ' \
242
+ 'MXRB_MENDIX_PAT_FILE also works without persisted login',
243
+ 'mxrb marketplace login --pat-file ~/.config/mxrb/pat'
244
+ ],
245
+ 'marketplace search' => ['marketplace search QUERY', 'Search official marketplace components',
246
+ 'mxrb marketplace search charts'],
247
+ 'marketplace show' => ['marketplace show NAME|CONTENT_ID', 'Show component metadata',
248
+ 'mxrb marketplace show charts'],
249
+ 'marketplace versions' => ['marketplace versions NAME|CONTENT_ID', 'List component versions and compatibility',
250
+ 'mxrb marketplace versions charts'],
251
+ 'marketplace pull' => ['marketplace pull NAME[@VERSION] [--mpr FILE]',
252
+ 'Download and import a marketplace release',
253
+ 'mxrb marketplace pull charts@2.1.0 --mpr Shop.mpr'],
254
+ 'marketplace import' => ['marketplace import FILE.mpk [--mpr FILE]', 'Import a local marketplace package',
255
+ 'mxrb marketplace import charts.mpk --mpr Shop.mpr'],
256
+ 'marketplace audit' => ['marketplace audit [--mpr FILE]', 'Audit locked updates and vulnerabilities',
257
+ 'mxrb marketplace audit --mpr Shop.mpr'],
258
+ 'marketplace list' => ['marketplace list [--mpr FILE]', 'List locked marketplace packages',
259
+ 'mxrb marketplace list --mpr Shop.mpr'],
260
+ 'marketplace verify' => ['marketplace verify [--mpr FILE]', 'Verify locked package checksums and identity',
261
+ 'mxrb marketplace verify --mpr Shop.mpr'],
262
+ 'marketplace dependencies' => ['marketplace dependencies NAME [--mpr FILE]', 'Resolve package dependencies',
263
+ 'mxrb marketplace dependencies charts --mpr Shop.mpr'],
264
+ 'marketplace update' => ['marketplace update NAME[@VERSION] [--mpr FILE] --apply',
265
+ 'Update an imported package', 'mxrb marketplace update charts --mpr Shop.mpr --apply'],
266
+ 'marketplace remove' => ['marketplace remove NAME [--mpr FILE] --apply', 'Remove an imported package safely',
267
+ 'mxrb marketplace remove charts --mpr Shop.mpr --apply'],
268
+ 'mda inspect' => ['mda inspect FILE.mda [--json]', 'Inspect archive contents and metadata',
269
+ 'mxrb mda inspect Shop.mda'],
270
+ 'mda compare' => ['mda compare LEFT.mda RIGHT.mda', 'Compare two deployment archives',
271
+ 'mxrb mda compare before.mda after.mda'],
272
+ 'migrate check' => ['migrate check [DIR] [--json]', 'Fail when project.rb and the MPR drift',
273
+ 'mxrb migrate check .'],
274
+ 'migrate plan' => ['migrate plan [DIR] [--json]', 'Preview project.rb and MPR drift', 'mxrb migrate plan .'],
275
+ 'module new' => ['module new NAME [--target DIR]', 'Create and connect an application module',
276
+ 'mxrb module new Billing'],
277
+ 'module search' => ['module search [QUERY]', 'Search the Ruby module catalog', 'mxrb module search payments'],
278
+ 'module add' => ['module add NAME|DIR [--target DIR]', 'Install and lock a Ruby module',
279
+ 'mxrb module add Billing'],
280
+ 'project inspect' => ['project inspect [DIR] [--json]', 'Inspect version, modules, MPRs and scaffolds',
281
+ 'mxrb project inspect .'],
282
+ 'scaffold list' => ['scaffold list [--target DIR]', 'List generators and registered scaffolds',
283
+ 'mxrb scaffold list'],
284
+ 'scaffold destroy' => ['scaffold destroy KIND:NAME [--target DIR]', 'Remove an unchanged registered scaffold',
285
+ 'mxrb scaffold destroy entity:Sales.Order'],
286
+ 'team-server login' => ['team-server login --pat-file FILE', 'Configure a Team Server PAT file',
287
+ 'mxrb team-server login --pat-file ~/.config/mxrb/pat'],
288
+ 'team-server projects' => ['team-server projects [--pat-file FILE]', 'List accessible Team Server apps',
289
+ 'mxrb team-server projects'],
290
+ 'team-server info' => ['team-server info APP_ID', 'Show Team Server app metadata',
291
+ 'mxrb team-server info APP_ID'],
292
+ 'team-server branches' => ['team-server branches APP_ID', 'List repository branches',
293
+ 'mxrb team-server branches APP_ID'],
294
+ 'team-server commits' => ['team-server commits APP_ID BRANCH', 'List commits on a branch',
295
+ 'mxrb team-server commits APP_ID main'],
296
+ 'team-server clone' => ['team-server clone APP_ID|URL TARGET', 'Clone and validate a Team Server app',
297
+ 'mxrb team-server clone APP_ID ./shop'],
298
+ 'team-server status' => ['team-server status DIR', 'Show local Team Server repository status',
299
+ 'mxrb team-server status ./shop'],
300
+ 'team-server fetch' => ['team-server fetch DIR', 'Fetch repository changes', 'mxrb team-server fetch ./shop'],
301
+ 'team-server pull' => ['team-server pull DIR', 'Pull repository changes', 'mxrb team-server pull ./shop'],
302
+ 'team-server push' => ['team-server push DIR', 'Push repository changes', 'mxrb team-server push ./shop'],
303
+ 'widgets sync' => ['widgets sync DEFINITION.rb FILE.mpr', 'Synchronize pluggable widget schemas',
304
+ 'mxrb widgets sync project.rb Shop.mpr']
305
+ }.to_h { |path, values| [path, Command.new(path, *values)] }.freeze
306
+
307
+ FEATURED = %w[init generate export run validate doctor update].freeze
308
+
309
+ def welcome(release: nil)
310
+ lines = ["mxrb #{Mxrb::VERSION} — Ruby-first Mendix toolkit"]
311
+ if release&.available?
312
+ lines << "Update available: #{release.latest} (installed: #{release.installed})"
313
+ lines << ' Review: mxrb changelog'
314
+ lines << ' Update: mxrb update'
315
+ end
316
+ lines.concat(['', 'Common next steps:'])
317
+ FEATURED.each do |name|
318
+ command = COMMANDS.fetch(name)
319
+ lines << format(' %-24s %s', command.example, command.summary)
320
+ end
321
+ lines.concat(['', 'Use `mxrb --help` for guided examples or `mxrb --commands` for every command.'])
322
+ lines.join("\n")
323
+ end
324
+
325
+ def overview
326
+ <<~HELP
327
+ mxrb #{Mxrb::VERSION} — Ruby-first Mendix toolkit
328
+
329
+ Start a new project:
330
+ mxrb init MyApp
331
+ cd MyApp
332
+ bundle install
333
+ bundle exec mxrb generate project.rb
334
+
335
+ Convert and run an existing Mendix application as Ruby + React:
336
+ mxrb export App.mpr app-ruby --mode ruby
337
+ mxrb run app-ruby
338
+
339
+ Understand an existing model:
340
+ mxrb doctor .
341
+ mxrb validate App.mpr
342
+ mxrb tree App.mpr
343
+
344
+ Discovery:
345
+ mxrb --commands List every top-level command
346
+ mxrb COMMAND --help Explain one command with an example
347
+ mxrb GROUP --commands List subcommands in a group
348
+
349
+ Updates:
350
+ mxrb update --check Check the latest published version
351
+ mxrb changelog Read release notes before updating
352
+ mxrb update Install the latest published version
353
+ HELP
354
+ end
355
+
356
+ def commands(prefix = nil)
357
+ return group_commands(prefix) if prefix && SUBCOMMANDS.key?(prefix)
358
+
359
+ entries = COMMANDS.values + scaffold_commands
360
+ width = entries.map { _1.name.length }.max
361
+ (["Available MXRB commands (#{entries.size}):", ''] + entries.sort_by(&:name).map do |command|
362
+ format(" %-#{width}s %s", command.name, command.summary)
363
+ end + ['', 'Run `mxrb COMMAND --help` for usage and an example.']).join("\n")
364
+ end
365
+
366
+ def command(path)
367
+ parts = Array(path).map(&:to_s)
368
+ parts[0] = 'diagram-er' if parts.first == 'domain-model'
369
+ name = parts.first
370
+ return Mxrb::Scaffold::Help.text(name) if scaffold_command?(name)
371
+
372
+ item = COMMANDS[name]
373
+ return unknown(parts) unless item
374
+
375
+ subcommand = parts[1]
376
+ detail = SUBCOMMAND_HELP[[name, subcommand].compact.join(' ')]
377
+ if detail
378
+ return ["Usage: mxrb #{detail.usage}", '', "#{detail.summary}.", '', 'Example:',
379
+ " #{detail.example}", '', "Parent help: mxrb #{name} --help"].join("\n")
380
+ end
381
+ body = ["Usage: mxrb #{item.usage}", '', "#{item.summary}.", '', 'Example:', " #{item.example}"]
382
+ if OPTIONS.key?(name)
383
+ body.concat(['', 'Options:'])
384
+ OPTIONS.fetch(name).each { |option, description| body << format(' %-22s %s', option, description) }
385
+ end
386
+ body.concat([''] + NOTES.fetch(name)) if NOTES.key?(name)
387
+ if SUBCOMMANDS.key?(name)
388
+ body.concat(['', group_commands(name, heading: 'Subcommands:')])
389
+ body << "\nUse `mxrb #{name} SUBCOMMAND --help` for layered help."
390
+ end
391
+ body.join("\n")
392
+ end
393
+
394
+ def known?(name)
395
+ COMMANDS.key?(name.to_s) || scaffold_command?(name.to_s)
396
+ end
397
+
398
+ def scaffold_command?(name)
399
+ Mxrb::Scaffold::Help::COMMANDS.key?(name) && !COMMANDS.key?(name)
400
+ end
401
+
402
+ def scaffold_commands
403
+ commands = Mxrb::Scaffold::Help::COMMANDS.reject do |name, _|
404
+ COMMANDS.key?(name)
405
+ end
406
+ commands.map do |name, (usage, summary, _destination)|
407
+ Command.new(name, "#{name} #{usage}", summary, "mxrb #{name} #{usage.split(/[|\[]/).first.strip}")
408
+ end
409
+ end
410
+
411
+ def group_commands(name, heading: nil)
412
+ rows = SUBCOMMANDS.fetch(name).map do |action|
413
+ detail = SUBCOMMAND_HELP["#{name} #{action}"]
414
+ format(' %-28s %s', "#{name} #{action}", detail&.summary.to_s)
415
+ end
416
+ ([heading || "Subcommands for `mxrb #{name}`:", ''] + rows).join("\n")
417
+ end
418
+
419
+ def unknown(path)
420
+ "No help is registered for `mxrb #{path.join(' ')}`. " \
421
+ 'Run `mxrb --commands` to list every command.'
422
+ end
423
+ end
424
+ # rubocop:enable Metrics
425
+ end
426
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'net/http'
6
+ require 'rbconfig'
7
+ require 'rubygems/version'
8
+ require 'time'
9
+ require 'uri'
10
+
11
+ module Mxrb
12
+ module CLI
13
+ class ReleaseError < StandardError; end
14
+
15
+ ReleaseStatus = Data.define(:installed, :latest, :checked_at) do
16
+ def available?
17
+ latest && Gem::Version.new(latest) > Gem::Version.new(installed)
18
+ rescue ArgumentError
19
+ false
20
+ end
21
+ end
22
+
23
+ # Checks RubyGems for versions and GitHub Releases for human release notes.
24
+ class Releases
25
+ LATEST_URI = URI('https://rubygems.org/api/v1/versions/mxrb/latest.json')
26
+ RELEASE_URI = 'https://api.github.com/repos/lucamykael/mxrb/releases/tags/v%s'
27
+ CACHE_TTL = 6 * 60 * 60
28
+
29
+ def initialize(installed: Mxrb::VERSION, cache_path: nil, clock: Time.method(:now), request: nil)
30
+ @installed = installed.to_s
31
+ @cache_path = cache_path || default_cache_path
32
+ @clock = clock
33
+ @request = request || method(:http_get)
34
+ end
35
+
36
+ def status(force: false)
37
+ payload = !force && read_cache
38
+ payload ||= fetch_latest
39
+ ReleaseStatus.new(@installed, payload && payload['version'], payload && payload['checked_at'])
40
+ rescue ReleaseError, JSON::ParserError, IOError, SystemCallError, Timeout::Error, SocketError
41
+ ReleaseStatus.new(@installed, nil, nil)
42
+ end
43
+
44
+ def changelog(version = nil) # rubocop:disable Metrics/AbcSize
45
+ selected = version.to_s.empty? ? status(force: true).latest : version.to_s
46
+ raise ReleaseError, 'Could not determine the latest published MXRB version' if selected.to_s.empty?
47
+
48
+ payload = JSON.parse(@request.call(URI(format(RELEASE_URI, selected))))
49
+ {
50
+ version: selected, title: payload['name'] || "mxrb #{selected}",
51
+ published_at: payload['published_at'], body: payload['body'].to_s,
52
+ url: payload['html_url'] || "https://github.com/lucamykael/mxrb/releases/tag/v#{selected}"
53
+ }
54
+ rescue ReleaseError, JSON::ParserError, IOError, SystemCallError, Timeout::Error, SocketError => e
55
+ raise ReleaseError, "Could not load changelog: #{e.message}"
56
+ end
57
+
58
+ private
59
+
60
+ def fetch_latest
61
+ data = JSON.parse(@request.call(LATEST_URI))
62
+ payload = { 'version' => data.fetch('version').to_s, 'checked_at' => @clock.call.utc.iso8601 }
63
+ write_cache(payload)
64
+ payload
65
+ end
66
+
67
+ def read_cache
68
+ return unless File.file?(@cache_path)
69
+
70
+ payload = JSON.parse(File.read(@cache_path))
71
+ checked_at = Time.iso8601(payload.fetch('checked_at'))
72
+ payload if @clock.call - checked_at < CACHE_TTL
73
+ rescue KeyError, ArgumentError
74
+ nil
75
+ end
76
+
77
+ def write_cache(payload)
78
+ FileUtils.mkdir_p(File.dirname(@cache_path))
79
+ temporary = "#{@cache_path}.#{Process.pid}.tmp"
80
+ File.write(temporary, JSON.generate(payload))
81
+ File.rename(temporary, @cache_path)
82
+ ensure
83
+ FileUtils.rm_f(temporary) if temporary
84
+ end
85
+
86
+ def default_cache_path
87
+ base = ENV['XDG_CACHE_HOME'].to_s
88
+ base = File.join(Dir.home, '.cache') if base.empty?
89
+ File.join(base, 'mxrb', 'release.json')
90
+ end
91
+
92
+ def http_get(uri)
93
+ request = Net::HTTP::Get.new(uri)
94
+ request['Accept'] = 'application/json'
95
+ request['User-Agent'] = "mxrb/#{Mxrb::VERSION}"
96
+ response = Net::HTTP.start(
97
+ uri.host, uri.port, use_ssl: true, open_timeout: 1.5, read_timeout: 2.0
98
+ ) { _1.request(request) }
99
+ raise ReleaseError, "release service returned HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
100
+
101
+ response.body
102
+ end
103
+ end
104
+
105
+ # Installs a published release while refusing to overwrite a source checkout.
106
+ class Updater
107
+ def initialize(releases: Releases.new, runner: Kernel.method(:system), source_root: nil, env: ENV)
108
+ @releases = releases
109
+ @runner = runner
110
+ @source_root = source_root || File.expand_path('../../..', __dir__)
111
+ @env = env
112
+ end
113
+
114
+ def update!
115
+ status = @releases.status(force: true)
116
+ raise ReleaseError, 'Could not check the latest published MXRB version' unless status.latest
117
+ return status unless status.available?
118
+ raise ReleaseError, checkout_message if source_checkout?
119
+
120
+ command = bundled? ? %w[bundle update mxrb] : [RbConfig.ruby, '-S', 'gem', 'update', 'mxrb', '--no-document']
121
+ raise ReleaseError, "Update command failed: #{command.join(' ')}" unless @runner.call(*command)
122
+
123
+ status
124
+ end
125
+
126
+ private
127
+
128
+ def source_checkout? = File.directory?(File.join(@source_root, '.git'))
129
+ def bundled? = !@env['BUNDLE_GEMFILE'].to_s.empty?
130
+
131
+ def checkout_message
132
+ 'This MXRB executable comes from a source checkout. Update it with `git pull` and `bundle install`; ' \
133
+ 'the CLI will not overwrite local source files.'
134
+ end
135
+ end
136
+ end
137
+ end
@@ -123,7 +123,7 @@ module Mxrb
123
123
 
124
124
  def page_category(type)
125
125
  return :client_action if type.end_with?('Action')
126
- return :custom_widget if type == 'CustomWidgets$CustomWidget'
126
+ return :custom_widget if type == 'CustomWidgets$CustomWidget' || !type.include?('$')
127
127
 
128
128
  :widget
129
129
  end
@@ -35,10 +35,17 @@ module Mxrb
35
35
  primitives(values).merge(
36
36
  key: widget_key, '$widgetId': widget_key, advanced: false,
37
37
  datasource: datasource, columns: columns.map { compile_column(_1) },
38
+ itemSelection: selection(values),
39
+ filtersPlaceholder: compile_widgets(values['filtersPlaceholder']&.last),
38
40
  class: css_class
39
41
  )
40
42
  end
41
43
 
44
+ def selection(values)
45
+ type = values['itemSelection']&.last&.fetch('Selection', 'None') || 'None'
46
+ raw_property('SelectionProperty', selectionType: type, dataSourceId: data_source_id)
47
+ end
48
+
42
49
  def primitives(values)
43
50
  values.each_with_object({}) do |(key, pair), result|
44
51
  type, value = pair
@@ -173,7 +180,7 @@ module Mxrb
173
180
 
174
181
  def raw_attribute(entity, name)
175
182
  {
176
- '$raw' => "AttributeProperty(#{js_object(
183
+ '$raw' => "ListAttributeProperty(#{js_object(
177
184
  path: '', entity:, attribute: name, attributeType: attribute_type(entity, name),
178
185
  sortable: true, filterable: true, dataSourceId: data_source_id, isList: false
179
186
  )})"
@@ -118,6 +118,9 @@ module Mxrb
118
118
 
119
119
  def compile_attribute_type(source)
120
120
  result = plain_document(source)
121
+ if result['$Type'] == 'DomainModels$StringAttributeType'
122
+ result['Length'] ||= Model::Attribute::DEFAULT_STRING_LENGTH
123
+ end
121
124
  if result['$Type'] == 'DomainModels$DateTimeAttributeType'
122
125
  result['LocalizeDate'] = source.fetch('LocalizeDate', true) == true
123
126
  end
@@ -12,10 +12,11 @@ module Mxrb
12
12
 
13
13
  attr_reader :data_source, :entity_name
14
14
 
15
- def initialize(source, page_name, widget)
15
+ def initialize(source, page_name, widget, render_widgets: nil)
16
16
  @source = source
17
17
  @page_name = page_name
18
18
  @widget = widget
19
+ @render_widgets = render_widgets
19
20
  @index = document_index
20
21
  @values = property_values(widget['Object'])
21
22
  @data_source = WebListDataSource.new(source, widget)
@@ -38,6 +39,7 @@ module Mxrb
38
39
  key: widget_key, '$widgetId': widget_key, datasource: raw(datasource),
39
40
  content: raw("TemplatedWidgetProperty({ children: () => #{content}, " \
40
41
  "dataSourceId: #{JSON.generate(data_source_id)}, editable: false })"),
42
+ filtersPlaceholder: filters_placeholder,
41
43
  itemSelection: raw(selection), class: css_class
42
44
  )
43
45
  "React.createElement($Gallery, #{js_object(values)})"
@@ -45,6 +47,14 @@ module Mxrb
45
47
 
46
48
  private
47
49
 
50
+ def filters_placeholder
51
+ widgets = array(@values['filtersPlaceholder']&.last&.fetch('Widgets', nil))
52
+ return [] if widgets.empty?
53
+ return :undefined unless @render_widgets
54
+
55
+ raw(@render_widgets.call(widgets, widget_key, entity_name))
56
+ end
57
+
48
58
  def datasource
49
59
  config = {
50
60
  dataSourceId: data_source_id,