mxrb 0.1.0

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 (237) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.de-DE.md +108 -0
  4. data/README.md +172 -0
  5. data/README.pt-BR.md +109 -0
  6. data/bin/mxrb +1613 -0
  7. data/docs/README.md +10 -0
  8. data/docs/de-DE/README.md +24 -0
  9. data/docs/de-DE/architectural-patterns.md +26 -0
  10. data/docs/de-DE/architectural-standard.md +48 -0
  11. data/docs/de-DE/architecture.md +76 -0
  12. data/docs/de-DE/compiler.md +162 -0
  13. data/docs/de-DE/conventions.md +19 -0
  14. data/docs/de-DE/design-system.md +52 -0
  15. data/docs/de-DE/entity-dsl.md +53 -0
  16. data/docs/de-DE/native-runtime-quality-report.md +74 -0
  17. data/docs/de-DE/oql-sql.md +211 -0
  18. data/docs/de-DE/platform-api-integration.md +36 -0
  19. data/docs/de-DE/platform-operations.md +243 -0
  20. data/docs/de-DE/project-structure.md +32 -0
  21. data/docs/de-DE/ruby-first-roadmap.md +116 -0
  22. data/docs/de-DE/scaffolds.md +59 -0
  23. data/docs/de-DE/semantic-refactoring.md +56 -0
  24. data/docs/de-DE/team-server.md +33 -0
  25. data/docs/de-DE/validation-matrix.md +64 -0
  26. data/docs/de-DE/vetclinic-acceptance.md +24 -0
  27. data/docs/de-DE/writing.md +138 -0
  28. data/docs/en-US/README.md +24 -0
  29. data/docs/en-US/architectural-patterns.md +151 -0
  30. data/docs/en-US/architectural-standard.md +89 -0
  31. data/docs/en-US/architecture.md +170 -0
  32. data/docs/en-US/compiler.md +157 -0
  33. data/docs/en-US/conventions.md +82 -0
  34. data/docs/en-US/design-system.md +163 -0
  35. data/docs/en-US/entity-dsl.md +53 -0
  36. data/docs/en-US/native-runtime-quality-report.md +73 -0
  37. data/docs/en-US/oql-sql.md +201 -0
  38. data/docs/en-US/platform-api-integration.md +36 -0
  39. data/docs/en-US/platform-operations.md +244 -0
  40. data/docs/en-US/project-structure.md +49 -0
  41. data/docs/en-US/ruby-first-roadmap.md +186 -0
  42. data/docs/en-US/scaffolds.md +57 -0
  43. data/docs/en-US/semantic-refactoring.md +198 -0
  44. data/docs/en-US/team-server.md +32 -0
  45. data/docs/en-US/validation-matrix.md +257 -0
  46. data/docs/en-US/vetclinic-acceptance.md +23 -0
  47. data/docs/en-US/writing.md +655 -0
  48. data/docs/pt-BR/README.md +24 -0
  49. data/docs/pt-BR/architectural-patterns.md +25 -0
  50. data/docs/pt-BR/architectural-standard.md +533 -0
  51. data/docs/pt-BR/architecture.md +300 -0
  52. data/docs/pt-BR/compiler.md +162 -0
  53. data/docs/pt-BR/conventions.md +19 -0
  54. data/docs/pt-BR/design-system.md +65 -0
  55. data/docs/pt-BR/entity-dsl.md +116 -0
  56. data/docs/pt-BR/native-runtime-quality-report.md +73 -0
  57. data/docs/pt-BR/oql-sql.md +208 -0
  58. data/docs/pt-BR/platform-api-integration.md +36 -0
  59. data/docs/pt-BR/platform-operations.md +260 -0
  60. data/docs/pt-BR/project-structure.md +32 -0
  61. data/docs/pt-BR/ruby-first-roadmap.md +239 -0
  62. data/docs/pt-BR/scaffolds.md +104 -0
  63. data/docs/pt-BR/semantic-refactoring.md +55 -0
  64. data/docs/pt-BR/team-server.md +59 -0
  65. data/docs/pt-BR/validation-matrix.md +86 -0
  66. data/docs/pt-BR/vetclinic-acceptance.md +50 -0
  67. data/docs/pt-BR/writing.md +177 -0
  68. data/examples/sudoku_evaluation.rb +14 -0
  69. data/examples/sudoku_functional_test.rb +28 -0
  70. data/lib/mxrb/architecture/graph.rb +124 -0
  71. data/lib/mxrb/architecture/validator.rb +98 -0
  72. data/lib/mxrb/benchmark.rb +33 -0
  73. data/lib/mxrb/compare.rb +390 -0
  74. data/lib/mxrb/compiler/adapter.rb +96 -0
  75. data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
  76. data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
  77. data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
  78. data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
  79. data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
  80. data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
  81. data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
  82. data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
  83. data/lib/mxrb/compiler/constants_materializer.rb +111 -0
  84. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
  85. data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
  86. data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
  87. data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
  88. data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
  89. data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
  90. data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
  91. data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
  92. data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
  93. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
  94. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
  95. data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
  96. data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
  97. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
  98. data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
  99. data/lib/mxrb/compiler/mda.rb +99 -0
  100. data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
  101. data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
  102. data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
  103. data/lib/mxrb/compiler/model_package.rb +116 -0
  104. data/lib/mxrb/compiler/model_values.rb +67 -0
  105. data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
  106. data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
  107. data/lib/mxrb/compiler/packager.rb +100 -0
  108. data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
  109. data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
  110. data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
  111. data/lib/mxrb/compiler/portable_packager.rb +356 -0
  112. data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
  113. data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
  114. data/lib/mxrb/compiler/project_materializer.rb +139 -0
  115. data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
  116. data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
  117. data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
  118. data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
  119. data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
  120. data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
  121. data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
  122. data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
  123. data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
  124. data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
  125. data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
  126. data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
  127. data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
  128. data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
  129. data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
  130. data/lib/mxrb/compiler/security_materializer.rb +104 -0
  131. data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
  132. data/lib/mxrb/compiler/settings_materializer.rb +25 -0
  133. data/lib/mxrb/compiler/source_model.rb +128 -0
  134. data/lib/mxrb/compiler/system_model_seed.rb +88 -0
  135. data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
  136. data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
  137. data/lib/mxrb/compiler/translation_materializer.rb +95 -0
  138. data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
  139. data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
  140. data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
  141. data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
  142. data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
  143. data/lib/mxrb/doctor.rb +116 -0
  144. data/lib/mxrb/dsl/builder.rb +1636 -0
  145. data/lib/mxrb/errors.rb +18 -0
  146. data/lib/mxrb/evaluation.rb +131 -0
  147. data/lib/mxrb/exporter.rb +1716 -0
  148. data/lib/mxrb/frontend/migrator.rb +775 -0
  149. data/lib/mxrb/functional.rb +307 -0
  150. data/lib/mxrb/github/annotator.rb +161 -0
  151. data/lib/mxrb/initializer.rb +257 -0
  152. data/lib/mxrb/integrity/validator.rb +136 -0
  153. data/lib/mxrb/io/bson_codec.rb +145 -0
  154. data/lib/mxrb/io/mpr_file.rb +558 -0
  155. data/lib/mxrb/io/mxunit_codec.rb +42 -0
  156. data/lib/mxrb/marketplace.rb +380 -0
  157. data/lib/mxrb/model/association.rb +98 -0
  158. data/lib/mxrb/model/attribute.rb +100 -0
  159. data/lib/mxrb/model/connector.rb +17 -0
  160. data/lib/mxrb/model/design_materializer.rb +133 -0
  161. data/lib/mxrb/model/design_migration.rb +126 -0
  162. data/lib/mxrb/model/design_system.rb +111 -0
  163. data/lib/mxrb/model/domain_model.rb +51 -0
  164. data/lib/mxrb/model/entity.rb +180 -0
  165. data/lib/mxrb/model/menu.rb +38 -0
  166. data/lib/mxrb/model/microflow.rb +100 -0
  167. data/lib/mxrb/model/module.rb +142 -0
  168. data/lib/mxrb/model/navigation.rb +127 -0
  169. data/lib/mxrb/model/page.rb +410 -0
  170. data/lib/mxrb/model/project.rb +187 -0
  171. data/lib/mxrb/model/unit.rb +48 -0
  172. data/lib/mxrb/module_initializer.rb +75 -0
  173. data/lib/mxrb/official_marketplace/content_api.rb +292 -0
  174. data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
  175. data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
  176. data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
  177. data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
  178. data/lib/mxrb/official_marketplace.rb +720 -0
  179. data/lib/mxrb/oql/analyzer.rb +155 -0
  180. data/lib/mxrb/oql/index_advisor.rb +85 -0
  181. data/lib/mxrb/oql/plan_analyzer.rb +185 -0
  182. data/lib/mxrb/oql/server.rb +132 -0
  183. data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
  184. data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
  185. data/lib/mxrb/oql/workload_analyzer.rb +175 -0
  186. data/lib/mxrb/oql/workload_baseline.rb +60 -0
  187. data/lib/mxrb/oql.rb +424 -0
  188. data/lib/mxrb/project_lifecycle.rb +76 -0
  189. data/lib/mxrb/protocols/plan.rb +86 -0
  190. data/lib/mxrb/protocols.rb +123 -0
  191. data/lib/mxrb/runtime/database_workspace.rb +677 -0
  192. data/lib/mxrb/runtime/docker_executor.rb +41 -0
  193. data/lib/mxrb/runtime/docker_workspace.rb +29 -0
  194. data/lib/mxrb/runtime/executor.rb +169 -0
  195. data/lib/mxrb/runtime/java_locator.rb +26 -0
  196. data/lib/mxrb/runtime/native.rb +474 -0
  197. data/lib/mxrb/runtime/sql_server_database.rb +146 -0
  198. data/lib/mxrb/runtime/toolchain.rb +82 -0
  199. data/lib/mxrb/scaffold/cli.rb +113 -0
  200. data/lib/mxrb/scaffold/generator.rb +246 -0
  201. data/lib/mxrb/scaffold/help.rb +80 -0
  202. data/lib/mxrb/scaffold/page_templates.rb +54 -0
  203. data/lib/mxrb/scaffold/recipes.rb +198 -0
  204. data/lib/mxrb/scaffold/registry.rb +82 -0
  205. data/lib/mxrb/scaffold/templates.rb +723 -0
  206. data/lib/mxrb/scaffold/transaction.rb +84 -0
  207. data/lib/mxrb/schema/tables.rb +55 -0
  208. data/lib/mxrb/semantic/analyzer.rb +473 -0
  209. data/lib/mxrb/semantic/batch_plan.rb +68 -0
  210. data/lib/mxrb/semantic/domain_mutator.rb +419 -0
  211. data/lib/mxrb/semantic/embedder.rb +51 -0
  212. data/lib/mxrb/semantic/extractor.rb +290 -0
  213. data/lib/mxrb/semantic/index.rb +623 -0
  214. data/lib/mxrb/semantic/inliner.rb +216 -0
  215. data/lib/mxrb/semantic/mover.rb +153 -0
  216. data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
  217. data/lib/mxrb/semantic/remover.rb +70 -0
  218. data/lib/mxrb/semantic/renamer.rb +211 -0
  219. data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
  220. data/lib/mxrb/semantic/vec_store.rb +75 -0
  221. data/lib/mxrb/team_server.rb +390 -0
  222. data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
  223. data/lib/mxrb/templates/project/11.12.1.json +32 -0
  224. data/lib/mxrb/templates/project/6.10.8.json +50 -0
  225. data/lib/mxrb/templates/project/7.17.0.json +50 -0
  226. data/lib/mxrb/templates/project/7.5.0.json +50 -0
  227. data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
  228. data/lib/mxrb/version.rb +5 -0
  229. data/lib/mxrb/widget_package.rb +338 -0
  230. data/lib/mxrb/widget_synchronizer.rb +31 -0
  231. data/lib/mxrb/writer.rb +3775 -0
  232. data/lib/mxrb.rb +188 -0
  233. data/marketplace/catalog.json +10 -0
  234. data/marketplace/modules/shared-kernel/domain/README.md +4 -0
  235. data/marketplace/modules/shared-kernel/module.rb +5 -0
  236. data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
  237. metadata +447 -0
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Mxrb
6
+ module Compiler
7
+ # Copies project-owned libraries, resources, and compiled theme assets into deployment.
8
+ class DeploymentAssetCopier
9
+ include ModelValues
10
+
11
+ SAFE_IDENTIFIER = /\A[A-Za-z_][A-Za-z0-9_]*\z/
12
+
13
+ def initialize(project_root, deployment, copier, source: nil)
14
+ @project_root = project_root
15
+ @deployment = deployment
16
+ @copier = copier
17
+ @source = source
18
+ end
19
+
20
+ def copy
21
+ %w[userlib vendorlib].each do |name|
22
+ @copier.call(File.join(@project_root, name), File.join(@deployment, 'model', 'lib', 'userlib'))
23
+ end
24
+ copy_root('resources', 'model/resources')
25
+ copy_root('theme/web', 'web')
26
+ copy_root('theme-cache/web', 'web')
27
+ copy_theme_public_assets
28
+ export_images if @source
29
+ end
30
+
31
+ private
32
+
33
+ def copy_root(source, destination)
34
+ @copier.call(File.join(@project_root, source), File.join(@deployment, destination))
35
+ end
36
+
37
+ def copy_theme_public_assets
38
+ pattern = File.join(@project_root, 'themesource', '*', 'public')
39
+ Dir.glob(pattern).sort.each { @copier.call(_1, File.join(@deployment, 'web')) }
40
+ end
41
+
42
+ def export_images # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
43
+ directory = File.join(@deployment, 'web', 'img')
44
+ FileUtils.mkdir_p(directory)
45
+ @source.units_of('Images$ImageCollection').each do |unit|
46
+ collection = unit.document
47
+ array(collection['Images']).each do |item|
48
+ names = [unit.module_name, collection['Name'], item['Name']]
49
+ unless names.all? { _1.to_s.match?(SAFE_IDENTIFIER) }
50
+ raise CompilationError, "unsafe image identifier #{names.inspect}"
51
+ end
52
+
53
+ filename = "#{names.join('$')}.#{image_format(item)}"
54
+ File.binwrite(File.join(directory, filename), image_bytes(item['Image']))
55
+ end
56
+ end
57
+ end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'zip'
6
+
7
+ module Mxrb
8
+ module Compiler
9
+ DeploymentBootstrap = Data.define(:deployment, :created, :template_root)
10
+
11
+ # Creates a deployment workspace from versioned Studio Pro templates without invoking its builders.
12
+ class DeploymentBootstrapper # rubocop:disable Metrics/ClassLength
13
+ COMMON_TEMPLATE_ROOTS = %w[data log model native run tmp].freeze
14
+ REQUIRED_DIRECTORIES = %w[
15
+ data/database data/files data/tmp log model/bundles model/lib/userlib model/resources run tmp web/img
16
+ ].freeze
17
+
18
+ def initialize(mpr_path, deployment:, mendix_home: nil)
19
+ @mpr_path = File.expand_path(mpr_path)
20
+ @project_root = File.dirname(@mpr_path)
21
+ @deployment = File.expand_path(deployment)
22
+ @source = SourceModel.read(@mpr_path)
23
+ @version_root = resolve_version_root(mendix_home)
24
+ @template_root = File.join(@version_root, 'modeler', 'runtemplates', 'deployment')
25
+ end
26
+
27
+ def prepare
28
+ return prepare_existing if ready?
29
+
30
+ validate_templates!
31
+ prepare_fresh
32
+ end
33
+
34
+ def prepare_existing
35
+ ensure_web_build_templates if modern_react_web?
36
+ ensure_required_directories
37
+ copy_project_assets
38
+ finalize_web_shell
39
+ DeploymentBootstrap.new(deployment: @deployment, created: false, template_root: @template_root)
40
+ end
41
+
42
+ def prepare_fresh
43
+ FileUtils.mkdir_p(@deployment)
44
+ copy_version_templates
45
+ ensure_required_directories
46
+ render_templates
47
+ initialize_model
48
+ copy_project_assets
49
+ finalize_web_shell
50
+ DeploymentBootstrap.new(deployment: @deployment, created: true, template_root: @template_root)
51
+ end
52
+
53
+ private
54
+
55
+ def ready?
56
+ Adapter::REQUIRED_FILES.all? { File.file?(File.join(@deployment, _1)) }
57
+ end
58
+
59
+ def resolve_version_root(mendix_home)
60
+ root = File.expand_path(mendix_home || File.join(Dir.home, '.local', 'share', 'mendix', @source.version))
61
+ File.basename(root) == 'runtime' ? File.dirname(root) : root
62
+ end
63
+
64
+ def validate_templates!
65
+ return if File.directory?(@template_root) || File.file?(deployment_archive)
66
+
67
+ raise CompilationError, "Mendix #{@source.version} deployment templates not found at #{@template_root}"
68
+ end
69
+
70
+ def copy_version_templates
71
+ extract_deployment_archive if legacy_archive?
72
+ COMMON_TEMPLATE_ROOTS.each { copy_tree(File.join(@template_root, _1), File.join(@deployment, _1)) }
73
+ web_template_roots.each do |source_name, destination_name|
74
+ copy_tree(File.join(@template_root, source_name), File.join(@deployment, destination_name))
75
+ end
76
+ end
77
+
78
+ def web_template_roots
79
+ return { 'react-web' => 'web' } if modern_react_web?
80
+ return { 'dojo-web' => 'web' } if @source.version.to_i == 10
81
+ return { 'web' => 'web', 'modern-web' => 'modern-web' } if @source.version.to_i == 9
82
+
83
+ { 'web' => 'web' }
84
+ end
85
+
86
+ def modern_react_web?
87
+ @source.version.to_i >= 11 || (@source.version.to_i == 10 && @source.optimized_web_client?)
88
+ end
89
+
90
+ def legacy_archive? = @source.version.to_i <= 7 && File.file?(deployment_archive)
91
+ def deployment_archive = File.join(@version_root, 'modeler', 'deployment.mxz')
92
+
93
+ def extract_deployment_archive # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
94
+ root = "#{File.expand_path(@deployment)}#{File::SEPARATOR}"
95
+ Zip::File.open(deployment_archive) do |archive|
96
+ archive.each do |entry|
97
+ destination = File.expand_path(entry.name, @deployment)
98
+ unless destination.start_with?(root)
99
+ raise CompilationError, "unsafe deployment template entry #{entry.name.inspect}"
100
+ end
101
+
102
+ entry.directory? ? FileUtils.mkdir_p(destination) : extract_template_entry(entry, destination)
103
+ end
104
+ end
105
+ rescue Zip::Error => e
106
+ raise CompilationError, "invalid deployment template #{deployment_archive}: #{e.message}"
107
+ end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
108
+
109
+ def extract_template_entry(entry, destination)
110
+ return if File.exist?(destination)
111
+
112
+ FileUtils.mkdir_p(File.dirname(destination))
113
+ entry.get_input_stream do |input|
114
+ File.open(destination, 'wb') { |output| ::IO.copy_stream(input, output) }
115
+ end
116
+ end
117
+
118
+ def copy_tree(source, destination)
119
+ return unless File.directory?(source)
120
+
121
+ FileUtils.mkdir_p(destination)
122
+ Dir.children(source).sort.each do |name|
123
+ from = File.join(source, name)
124
+ to = File.join(destination, name)
125
+ next if File.exist?(to)
126
+
127
+ File.directory?(from) ? copy_tree(from, to) : FileUtils.cp(from, to)
128
+ end
129
+ end
130
+
131
+ def render_templates
132
+ Dir.glob(File.join(@deployment, '**', '*.template')).sort.each do |source|
133
+ destination = source.delete_suffix('.template')
134
+ File.write(destination, render(File.read(source)))
135
+ FileUtils.rm_f(source)
136
+ end
137
+ end
138
+
139
+ def ensure_web_build_templates
140
+ validate_templates!
141
+ %w[rspack.config.mjs.template rollup.config.mjs.template].each do |name|
142
+ source = File.join(@template_root, 'react-web', name)
143
+ destination = File.join(@deployment, 'web', name)
144
+ FileUtils.cp(source, destination) unless File.exist?(destination.delete_suffix('.template'))
145
+ end
146
+ render_templates
147
+ end
148
+
149
+ def render(content)
150
+ replacements.reduce(content) { |result, (token, value)| result.gsub(token, value) }
151
+ .gsub('\\{', '{').gsub('\\}', '}')
152
+ end
153
+
154
+ def replacements
155
+ {
156
+ '{InstallDir:JsStringEncode}' => @version_root,
157
+ '{InstallDir:HtmlAttributeEncode}' => @version_root,
158
+ '{DeploymentDir:JsStringEncode}' => @deployment,
159
+ '{DeploymentDir:HtmlAttributeEncode}' => @deployment,
160
+ '{ProjectDir:HtmlAttributeEncode}' => @project_root,
161
+ '{ProjectName:HtmlAttributeEncode}' => project_name,
162
+ '{EnableWatchman:true|false}' => 'false', '{MxBuildNumber}' => "mxrb-#{@source.version}"
163
+ }
164
+ end
165
+
166
+ def initialize_model
167
+ model = File.join(@deployment, 'model')
168
+ FileUtils.mkdir_p(model)
169
+ SystemModelSeed.for(@source.version).package.write(File.join(model, 'model.mdp'))
170
+ initialize_json_files(model)
171
+ ProjectJarArchive.new(@mpr_path, @project_root, @deployment)
172
+ .write(File.join(model, 'bundles', 'project.jar'), empty_classes)
173
+ end
174
+
175
+ def initialize_json_files(model)
176
+ metadata = DeploymentMetadata.new(@mpr_path, @source)
177
+ write_json(File.join(model, 'metadata.json'), metadata.document)
178
+ write_json(File.join(model, 'dependencies.json'), metadata.dependencies)
179
+ write_json(File.join(model, 'microflows.json'), {})
180
+ write_json(File.join(model, 'operations.json'), [])
181
+ end
182
+
183
+ def empty_classes
184
+ path = File.join(@deployment, 'tmp', 'mxrb-empty-classes')
185
+ FileUtils.mkdir_p(path)
186
+ path
187
+ end
188
+
189
+ def project_name = File.basename(@mpr_path, File.extname(@mpr_path))
190
+
191
+ def write_json(path, value) = File.write(path, JSON.pretty_generate(value))
192
+
193
+ def copy_project_assets
194
+ DeploymentAssetCopier.new(
195
+ @project_root, @deployment, method(:copy_tree), source: @source
196
+ ).copy
197
+ end
198
+
199
+ def ensure_required_directories
200
+ REQUIRED_DIRECTORIES.each { FileUtils.mkdir_p(File.join(@deployment, _1)) }
201
+ end
202
+
203
+ def finalize_web_shell
204
+ WebShellMaterializer.new(File.join(@deployment, 'web'), version: @source.version).materialize
205
+ end
206
+ end # rubocop:enable Metrics/ClassLength
207
+ end
208
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ DeploymentMaterialization = Data.define(:deployment, :mendix_version, :stages)
6
+
7
+ # Runs the complete native MPR-to-deployment model pipeline in dependency order.
8
+ class DeploymentMaterializer
9
+ STAGES = [
10
+ SecurityMaterializer, ConstantsMaterializer, DomainModelMaterializer,
11
+ ArtifactMaterializer, TranslationMaterializer, SystemTextMaterializer,
12
+ SystemQueueMaterializer, ClientModelMaterializer,
13
+ CodeActionMaterializer, SettingsMaterializer, MicroflowMaterializer, ProjectMaterializer
14
+ ].freeze
15
+
16
+ def initialize(mpr_path, deployment: nil, mendix_home: nil)
17
+ @mpr_path = File.expand_path(mpr_path)
18
+ @deployment = File.expand_path(deployment || File.join(File.dirname(@mpr_path), 'deployment'))
19
+ @mendix_home = mendix_home
20
+ end
21
+
22
+ def materialize
23
+ source = SourceModel.read(@mpr_path)
24
+ DeploymentBootstrapper.new(
25
+ @mpr_path, deployment: @deployment, mendix_home: @mendix_home
26
+ ).prepare
27
+ Adapter.for(source.version).validate_deployment!(@deployment)
28
+ results = materialize_stages
29
+ DeploymentMaterialization.new(
30
+ deployment: @deployment, mendix_version: source.version, stages: results.freeze
31
+ )
32
+ end
33
+
34
+ private
35
+
36
+ def materialize_stages
37
+ STAGES.to_h do |stage|
38
+ [stage.name.split('::').last, stage.new(@mpr_path, deployment: @deployment).materialize]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Derives initial Runtime metadata from an application MPR.
6
+ class DeploymentMetadata
7
+ def initialize(mpr_path, source)
8
+ @mpr_path = mpr_path
9
+ @source = source
10
+ end
11
+
12
+ def document
13
+ {
14
+ 'RuntimeVersion' => @source.version, 'ProjectID' => project_id,
15
+ 'ProjectName' => project_name, 'ModelVersion' => 'unversioned', 'Description' => '',
16
+ 'AdminUser' => 'MxAdmin', 'AdminRole' => '', 'JavaVersion' => java_version,
17
+ 'Roles' => {}, 'Constants' => [], 'ScheduledEvents' => [], 'Languages' => languages,
18
+ 'Configuration' => configuration, 'RequestHandlers' => request_handlers,
19
+ 'ModelRuntimeCompatibilityHash' => ''
20
+ }
21
+ end
22
+
23
+ def dependencies
24
+ { 'schemaVersion' => '2.3', 'appName' => project_name, 'published' => [], 'consumed' => [] }
25
+ end
26
+
27
+ private
28
+
29
+ def project_id
30
+ @source.units.find { _1.document['$Type'] == 'Projects$Project' }&.id.to_s
31
+ end
32
+
33
+ def project_name = File.basename(@mpr_path, File.extname(@mpr_path))
34
+ def java_version = Runtime::Toolchain.new(@mpr_path).plan.java_version.to_i
35
+
36
+ def languages
37
+ @source.documents.flat_map { collect_languages(_1) }.uniq.sort
38
+ end
39
+
40
+ def collect_languages(value)
41
+ case value
42
+ when Hash
43
+ own = value['$Type'] == 'Texts$Translation' ? [value['LanguageCode'].to_s] : []
44
+ own + value.values.flat_map { collect_languages(_1) }
45
+ when Array then value.flat_map { collect_languages(_1) }
46
+ else []
47
+ end.reject(&:empty?)
48
+ end
49
+
50
+ def configuration
51
+ {
52
+ 'SourceDatabaseType' => 'HSQLDB', 'SourceDatabaseName' => 'home',
53
+ 'SourceBuiltInDatabasePath' => 'model/sampledata/data/database'
54
+ }
55
+ end
56
+
57
+ def request_handlers
58
+ [['/api/', true], ['/link/', true], ['/p/', false]].map do |name, enabled|
59
+ { 'Name' => name, 'DefaultEnabled' => enabled, 'MatchExactly' => false }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Converts domain-model documents from editor shape to Runtime shape.
6
+ class DomainDocumentCompiler # rubocop:disable Metrics/ClassLength
7
+ include ModelValues
8
+
9
+ def initialize(source)
10
+ @source = source
11
+ domain_units = source.respond_to?(:units_of) ? source.units_of('DomainModels$DomainModel') : []
12
+ @entities = domain_units.each_with_object({}) do |unit, result|
13
+ array(unit.document['Entities']).each do |entity|
14
+ result["#{unit.module_name}.#{entity['Name']}"] = entity
15
+ end
16
+ end
17
+ @security = DomainSecurityCompiler.new(source)
18
+ end
19
+
20
+ def compile(unit) # rubocop:disable Metrics/AbcSize
21
+ source = unit.document
22
+ {
23
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
24
+ 'Entities' => array(source['Entities']).map { compile_entity(_1, unit.module_name) },
25
+ 'Associations' => array(source['Associations']).map { @security.association(_1, unit.module_name) },
26
+ 'CrossAssociations' => array(source['CrossAssociations']).map do |association|
27
+ @security.association(association, unit.module_name)
28
+ end
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def compile_entity(source, module_name)
35
+ name = source['Name'].to_s
36
+ entity_content(source).merge(
37
+ 'Source' => compile_entity_source(source['Source']),
38
+ 'GUID' => source['GUID'], 'Image' => source['Image'].to_s,
39
+ 'QualifiedName' => "#{module_name}.#{name}", 'UnqualifiedName' => name
40
+ )
41
+ end
42
+
43
+ def compile_entity_source(source) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
44
+ return nil unless source
45
+ return plain_document(source) unless source['$Type'] == 'DomainModels$OqlViewEntitySource'
46
+
47
+ qualified_name = source['SourceDocument'].to_s
48
+ document = @source.units.find do |unit|
49
+ unit.document['$Type'] == 'DomainModels$ViewEntitySourceDocument' &&
50
+ "#{unit.module_name}.#{unit.document['Name']}" == qualified_name
51
+ end
52
+ raise CompilationError, "OQL view source #{qualified_name.inspect} not found" unless document
53
+
54
+ source.slice('$ID', '$Type', 'SourceDocument').merge(
55
+ 'OqlRuntime' => document.document['Oql'].to_s,
56
+ 'SourceDocumentName' => qualified_name,
57
+ 'SourceType' => 'OQL'
58
+ )
59
+ end
60
+
61
+ def entity_content(source) # rubocop:disable Metrics/AbcSize
62
+ {
63
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
64
+ 'MaybeGeneralization' => compile_generalization(source['MaybeGeneralization']),
65
+ 'Attributes' => array(source['Attributes']).map { compile_attribute(_1) },
66
+ 'ValidationRules' => array(source['ValidationRules']).map { compile_validation(_1) },
67
+ 'Events' => plain_array(source['Events']),
68
+ 'Indexes' => array(source['Indexes']).map { compile_index(_1) },
69
+ 'AccessRules' => array(source['AccessRules']).map { @security.access_rule(_1) }
70
+ }
71
+ end
72
+
73
+ def compile_generalization(source)
74
+ type = source.fetch('$Type')
75
+ result = { '$ID' => source['$ID'], '$Type' => type }
76
+ result['Key'] = source['Key'] if type == 'DomainModels$NoGeneralization'
77
+ result.merge(generalization_flags(source, type))
78
+ .merge('Generalization' => source['Generalization'].to_s)
79
+ end
80
+
81
+ def generalization_flags(source, type)
82
+ inherited = inherited_flags(source['Generalization']) if type == 'DomainModels$Generalization'
83
+ {
84
+ 'Persistable' => source.fetch('Persistable', inherited&.fetch('Persistable', false)) == true,
85
+ 'HasCreatedDateAttr' => source.fetch('HasCreatedDateAttr',
86
+ inherited&.fetch('HasCreatedDateAttr', false)) == true,
87
+ 'HasChangedDateAttr' => source.fetch('HasChangedDateAttr',
88
+ inherited&.fetch('HasChangedDateAttr', false)) == true,
89
+ 'HasOwnerAttr' => source.fetch('HasOwnerAttr', inherited&.fetch('HasOwnerAttr', false)) == true,
90
+ 'HasChangedByAttr' => source.fetch('HasChangedByAttr', inherited&.fetch('HasChangedByAttr', false)) == true
91
+ }
92
+ end
93
+
94
+ def inherited_flags(qualified_name)
95
+ return system_flags if qualified_name.to_s.start_with?('System.')
96
+
97
+ parent = @entities[qualified_name.to_s]
98
+ return system_flags unless parent
99
+
100
+ generalization = parent['MaybeGeneralization'] || {}
101
+ generalization_flags(generalization, generalization['$Type'])
102
+ end
103
+
104
+ def system_flags
105
+ {
106
+ 'Persistable' => true, 'HasCreatedDateAttr' => true, 'HasChangedDateAttr' => true,
107
+ 'HasOwnerAttr' => true, 'HasChangedByAttr' => true
108
+ }
109
+ end
110
+
111
+ def compile_attribute(source)
112
+ {
113
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
114
+ 'Value' => plain_document(source['Value']), 'Type' => compile_attribute_type(source['NewType']),
115
+ 'Name' => source['Name'], 'GUID' => source['GUID']
116
+ }
117
+ end
118
+
119
+ def compile_attribute_type(source)
120
+ result = plain_document(source)
121
+ if result['$Type'] == 'DomainModels$DateTimeAttributeType'
122
+ result['LocalizeDate'] = source.fetch('LocalizeDate', true) == true
123
+ end
124
+ result
125
+ end
126
+
127
+ def compile_validation(source)
128
+ message = source['Message'] || {}
129
+ {
130
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
131
+ 'Message' => message.slice('$ID', '$Type'),
132
+ 'RuleInfo' => plain_document(source['RuleInfo']), 'Attribute' => source['Attribute'].to_s
133
+ }
134
+ end
135
+
136
+ def compile_index(source)
137
+ {
138
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
139
+ 'Attributes' => array(source['Attributes']).map { compile_indexed_attribute(_1) },
140
+ 'GUID' => source['GUID'], 'IncludeInOffline' => source['IncludeInOffline'] == true
141
+ }
142
+ end
143
+
144
+ def compile_indexed_attribute(source)
145
+ result = plain_document(source)
146
+ result['AssociationPointer'] ||= BSON::Binary.new(
147
+ IO::BsonCodec.uuid_to_blob('00000000-0000-0000-0000-000000000000'), :generic
148
+ )
149
+ result
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ DomainModelMaterialization = Data.define(:model_path, :domain_models, :entities)
6
+
7
+ # Replaces or inserts Runtime domain-model documents in model.mdp.
8
+ class DomainModelMaterializer
9
+ def initialize(mpr_path, deployment:)
10
+ @mpr_path = File.expand_path(mpr_path)
11
+ @deployment = File.expand_path(deployment)
12
+ end
13
+
14
+ def materialize
15
+ source = SourceModel.read(@mpr_path)
16
+ Adapter.for(source.version)
17
+ compiler = DomainDocumentCompiler.new(source)
18
+ documents = source.units_of('DomainModels$DomainModel').map { compiler.compile(_1) }
19
+ path = File.join(@deployment, 'model', 'model.mdp')
20
+ write_model(path, documents)
21
+ DomainModelMaterialization.new(
22
+ model_path: path, domain_models: documents.length,
23
+ entities: documents.sum { _1.fetch('Entities').length }
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def write_model(path, documents)
30
+ package = documents.reduce(ModelPackage.read(path)) { |result, document| result.upsert(document) }
31
+ package.write(path)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Resolves project roles and association security into Runtime fields.
6
+ class DomainSecurityCompiler
7
+ include ModelValues
8
+
9
+ def initialize(source)
10
+ @role_map = project_role_map(source)
11
+ end
12
+
13
+ def access_rule(source)
14
+ module_roles = array(source['AllowedModuleRoles']).map(&:to_s)
15
+ {
16
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
17
+ 'MemberAccesses' => array(source['MemberAccesses']).map { member_access(_1) },
18
+ 'AllowedUserRoles' => module_roles.flat_map { @role_map.fetch(_1, []) }.uniq,
19
+ 'AllowCreate' => source['AllowCreate'] == true, 'AllowDelete' => source['AllowDelete'] == true,
20
+ 'XPathConstraint' => source['XPathConstraint'].to_s
21
+ }
22
+ end
23
+
24
+ def association(source, module_name)
25
+ name = source['Name'].to_s
26
+ result = association_fields(source).merge(
27
+ 'QualifiedName' => "#{module_name}.#{name}",
28
+ 'UnqualifiedName' => name, 'Navigability' => 'BothDirections'
29
+ )
30
+ child_key = source['$Type'] == 'DomainModels$CrossAssociation' ? 'Child' : 'ChildPointer'
31
+ result.merge(child_key => source[child_key])
32
+ end
33
+
34
+ private
35
+
36
+ def member_access(source)
37
+ {
38
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
39
+ 'Attribute' => source['Attribute'].to_s, 'Association' => source['Association'].to_s,
40
+ 'AccessRights' => source['AccessRights']
41
+ }
42
+ end
43
+
44
+ def association_fields(source)
45
+ {
46
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
47
+ 'DeleteBehavior' => delete_behavior(source['DeleteBehavior']),
48
+ 'Source' => source['Source'], 'GUID' => source['GUID'], 'Type' => source['Type'],
49
+ 'Owner' => source['Owner'], 'StorageFormat' => source['StorageFormat'],
50
+ 'ParentPointer' => source['ParentPointer']
51
+ }
52
+ end
53
+
54
+ def delete_behavior(source)
55
+ {
56
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
57
+ 'ParentErrorMessage' => text_reference(source['ParentErrorMessage']),
58
+ 'ChildErrorMessage' => text_reference(source['ChildErrorMessage']),
59
+ 'ParentDeleteBehavior' => runtime_delete_behavior(source['ParentDeleteBehavior']),
60
+ 'ChildDeleteBehavior' => runtime_delete_behavior(source['ChildDeleteBehavior'])
61
+ }
62
+ end
63
+
64
+ def text_reference(source)
65
+ source&.slice('$ID', '$Type')
66
+ end
67
+
68
+ def runtime_delete_behavior(value)
69
+ value == 'NoAction' ? 'DeleteMeAndReferences' : value
70
+ end
71
+
72
+ def project_role_map(source)
73
+ security = source.documents('Security$ProjectSecurity').first
74
+ return {} unless security
75
+
76
+ array(security['UserRoles']).each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |role, map|
77
+ array(role['ModuleRoles']).each { |module_role| map[module_role.to_s] << role['Name'].to_s }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end