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,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Mxrb
6
+ module Compiler
7
+ SecurityMaterialization = Data.define(:model_path, :metadata_path, :roles)
8
+
9
+ # Compiles project-security units into the flattened Runtime model shape.
10
+ class SecurityMaterializer
11
+ def initialize(mpr_path, deployment:)
12
+ @mpr_path = File.expand_path(mpr_path)
13
+ @deployment = File.expand_path(deployment)
14
+ end
15
+
16
+ def materialize
17
+ model_path = File.join(@deployment, 'model', 'model.mdp')
18
+ metadata_path = File.join(@deployment, 'model', 'metadata.json')
19
+ source = source_security
20
+ compiled, role_metadata = compile(source)
21
+ ModelPackage.read(model_path).upsert(compiled).write(model_path)
22
+ write_metadata(metadata_path, source, role_metadata)
23
+ SecurityMaterialization.new(model_path:, metadata_path:, roles: role_metadata.freeze)
24
+ end
25
+
26
+ private
27
+
28
+ def source_security
29
+ mpr = IO::MprFile.open(@mpr_path, readonly: true)
30
+ document = mpr.all_units.lazy.map { mpr.parse_contents(_1) }
31
+ .find { _1['$Type'] == 'Security$ProjectSecurity' }
32
+ raise CompilationError, 'MPR has no project security document' unless document
33
+
34
+ document
35
+ ensure
36
+ mpr&.close
37
+ end
38
+
39
+ def compile(source)
40
+ source_roles = array(source['UserRoles'])
41
+ names = source_roles.map { _1['Name'].to_s }
42
+ roles = source_roles.map { compile_role(_1, names) }
43
+ [compiled_document(source, roles), role_metadata(source_roles, names)]
44
+ end
45
+
46
+ def compiled_document(source, roles)
47
+ {
48
+ '$ID' => source['$ID'], '$Type' => source['$Type'], 'UserRoles' => roles,
49
+ 'DemoUsers' => array(source['DemoUsers']).map { compile_demo_user(_1) },
50
+ 'PasswordPolicySettings' => source['PasswordPolicySettings'],
51
+ 'SecurityLevel' => source['SecurityLevel'], 'AdminUserName' => source['AdminUserName'],
52
+ 'AdminUserRole' => source['AdminUserRole'],
53
+ 'EnableDemoUsers' => source['EnableDemoUsers'] == true,
54
+ 'EnableGuestAccess' => source['EnableGuestAccess'] == true,
55
+ 'GuestUserRole' => source['GuestUserRole'].to_s, 'StrictMode' => source['StrictMode'] == true
56
+ }
57
+ end
58
+
59
+ def compile_demo_user(user)
60
+ user.merge('UserRoles' => array(user['UserRoles']).map(&:to_s))
61
+ end
62
+
63
+ def compile_role(role, names)
64
+ manageable = role['ManageAllRoles'] == true ? names : array(role['ManageableRoles']).map(&:to_s)
65
+ {
66
+ '$ID' => role['$ID'], '$Type' => role['$Type'], 'GUID' => role['GUID'],
67
+ 'Name' => role['Name'], 'Description' => role['Description'].to_s,
68
+ 'IsSystemAdministrator' => array(role['ModuleRoles']).include?('System.Administrator'),
69
+ 'ManageableRoles' => manageable,
70
+ 'ManageUsersWithoutRoles' => role['ManageUsersWithoutRoles'] == true,
71
+ 'CheckSecurity' => role['CheckSecurity'] != false
72
+ }
73
+ end
74
+
75
+ def role_metadata(roles, names)
76
+ ids = roles.to_h { [_1['Name'].to_s, id(_1['GUID'])] }
77
+ roles.to_h do |role|
78
+ [ids.fetch(role['Name'].to_s), metadata_role(role, names, ids)]
79
+ end
80
+ end
81
+
82
+ def metadata_role(role, names, ids)
83
+ manageable = role['ManageAllRoles'] == true ? names : array(role['ManageableRoles']).map(&:to_s)
84
+ resolved = manageable.filter_map { ids[_1] }
85
+ { 'Name' => role['Name'].to_s }.tap do |value|
86
+ value['ManageableRoles'] = resolved unless resolved.empty?
87
+ end
88
+ end
89
+
90
+ def write_metadata(path, source, roles)
91
+ metadata = JSON.parse(File.read(path))
92
+ metadata['Roles'] = roles
93
+ admin = roles.find { |_role_id, value| value['Name'] == source['AdminUserRole'] }
94
+ raise CompilationError, "admin role #{source['AdminUserRole'].inspect} is not defined" unless admin
95
+
96
+ metadata['AdminRole'] = admin.first
97
+ File.write(path, JSON.pretty_generate(metadata))
98
+ end
99
+
100
+ def array(value) = IO::BsonCodec.parse_array(value)[:items]
101
+ def id(value) = IO::BsonCodec.extract_id(value)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Compiles project settings using the ordered, version-matched Runtime schema.
6
+ class SettingsDocumentCompiler
7
+ include ModelValues
8
+
9
+ def initialize(schema)
10
+ @schema = schema
11
+ end
12
+
13
+ def compile(unit)
14
+ source = unit.document
15
+ unless source['$Type'] == 'Settings$ProjectSettings'
16
+ raise CompilationError,
17
+ "unsupported settings root #{source['$Type']}"
18
+ end
19
+
20
+ {
21
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
22
+ 'Settings' => array(source['Settings']).map { compile_node(_1) }
23
+ }
24
+ end
25
+
26
+ private
27
+
28
+ def compile_value(value)
29
+ case value
30
+ when Hash then value['$Type'] ? compile_node(value) : value.transform_values { compile_value(_1) }
31
+ when Array then array(value).map { compile_value(_1) }
32
+ else value
33
+ end
34
+ end
35
+
36
+ def compile_node(source)
37
+ existing = @schema.counterpart(source)
38
+ @schema.fields_for(source).to_h do |field|
39
+ value = source.key?(field) ? compile_value(source[field]) : existing&.fetch(field, nil)
40
+ [field, value]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ SettingsMaterialization = Data.define(:model_path, :documents)
6
+
7
+ # Synchronizes Runtime-visible project settings, including lifecycle microflows.
8
+ class SettingsMaterializer
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
+ path = File.join(@deployment, 'model', 'model.mdp')
17
+ package = ModelPackage.read(path)
18
+ compiler = SettingsDocumentCompiler.new(RuntimeModelSchema.new(package, version: source.version))
19
+ documents = source.units_of('Settings$ProjectSettings').map { compiler.compile(_1) }
20
+ documents.reduce(package) { |result, document| result.upsert(document) }.write(path)
21
+ SettingsMaterialization.new(model_path: path, documents: documents.length)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Read-only indexed view of MPR units used by native materializers.
6
+ class SourceModel
7
+ Unit = Data.define(:id, :container_id, :containment, :document, :module_name)
8
+ CLIENT_TYPE = /\A(?:Forms|JavaScriptActions|Menus|Navigation)\$|\AMicroflows\$Nanoflow\z/
9
+
10
+ attr_reader :path, :version, :units
11
+
12
+ def self.read(path) = new(File.expand_path(path))
13
+
14
+ def initialize(path)
15
+ @path = path
16
+ read_units
17
+ end
18
+
19
+ def documents(type = nil)
20
+ selected = type ? units.select { _1.document['$Type'] == type } : units
21
+ selected.map(&:document)
22
+ end
23
+
24
+ def units_of(type)
25
+ @units_by_type ||= units.group_by { _1.document['$Type'] }.transform_values(&:freeze).freeze
26
+ @units_by_type.fetch(type, EMPTY_UNITS)
27
+ end
28
+
29
+ def document_index
30
+ @document_index ||= {}.tap do |index|
31
+ units.each { index_document(_1.document, index) }
32
+ end.freeze
33
+ end
34
+
35
+ def web_page?(unit)
36
+ return false if unit.document['Excluded'] == true
37
+
38
+ layout_name = unit.document.dig('FormCall', 'Form').to_s
39
+ layout = units_of('Forms$Layout').find do |candidate|
40
+ "#{candidate.module_name}.#{candidate.document['Name']}" == layout_name
41
+ end
42
+ layout.nil? || layout.document.dig('Content', '$Type') != 'Forms$NativeLayoutContent'
43
+ end
44
+
45
+ def web_layout?(unit)
46
+ unit.document['Excluded'] != true &&
47
+ unit.document.dig('Content', '$Type') == 'Forms$WebLayoutContent'
48
+ end
49
+
50
+ def optimized_web_client?
51
+ settings = documents('Settings$ProjectSettings').first
52
+ web = nested_values(settings).find { _1.is_a?(Hash) && _1['$Type'] == 'Forms$WebUIProjectSettingsPart' }
53
+ web&.fetch('UseOptimizedClient', nil).to_s == 'Yes'
54
+ end
55
+
56
+ def client_reference?(qualified_name)
57
+ reference = /(?:\A|[^A-Za-z0-9_.])@#{Regexp.escape(qualified_name)}(?![A-Za-z0-9_.])/
58
+ units.any? do |unit|
59
+ unit.document['$Type'].to_s.match?(CLIENT_TYPE) && deep_string_match?(unit.document, reference)
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ EMPTY_UNITS = [].freeze
66
+
67
+ def read_units
68
+ mpr = IO::MprFile.open(path, readonly: true)
69
+ @version = mpr.mendix_version
70
+ raw_units = mpr.all_units
71
+ documents = raw_units.to_h { [_1['UnitID'], mpr.parse_contents(_1)] }
72
+ @units = build_units(raw_units, documents)
73
+ ensure
74
+ mpr&.close
75
+ end
76
+
77
+ def build_units(raw_units, documents)
78
+ raw_index = raw_units.to_h { [_1['UnitID'], _1] }
79
+ module_names = {}
80
+ raw_units.map do |raw|
81
+ Unit.new(
82
+ id: raw['UnitID'], container_id: raw['ContainerID'],
83
+ containment: raw['ContainmentName'], document: documents.fetch(raw['UnitID']),
84
+ module_name: resolve_module(raw, raw_index, documents, module_names)
85
+ )
86
+ end.freeze
87
+ end
88
+
89
+ def resolve_module(raw, raw_index, documents, cache)
90
+ id = raw['UnitID']
91
+ return cache[id] if cache.key?(id)
92
+
93
+ document = documents.fetch(id)
94
+ return cache[id] = document['Name'].to_s if document['$Type'] == 'Projects$ModuleImpl'
95
+ return cache[id] = nil if raw['ContainerID'] == id
96
+
97
+ parent = raw_index[raw['ContainerID']]
98
+ cache[id] = parent && resolve_module(parent, raw_index, documents, cache)
99
+ end
100
+
101
+ def deep_string_match?(value, pattern)
102
+ case value
103
+ when Hash then value.any? { |_key, child| deep_string_match?(child, pattern) }
104
+ when Array then value.any? { deep_string_match?(_1, pattern) }
105
+ else value.is_a?(String) && value.match?(pattern)
106
+ end
107
+ end
108
+
109
+ def nested_values(value)
110
+ case value
111
+ when Hash then [value, *value.values.flat_map { nested_values(_1) }]
112
+ when Array then value.flat_map { nested_values(_1) }
113
+ else []
114
+ end
115
+ end
116
+
117
+ def index_document(value, index)
118
+ case value
119
+ when Hash
120
+ id = IO::BsonCodec.extract_id(value['$ID'])
121
+ index[id] = value if id
122
+ value.each_value { index_document(_1, index) }
123
+ when Array then value.each { index_document(_1, index) }
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'rubygems/version'
5
+ require 'zlib'
6
+
7
+ module Mxrb
8
+ module Compiler
9
+ # Versioned Runtime-owned System module omitted from application MPR files.
10
+ class SystemModelSeed
11
+ MODULE_ID = '6e3fe785-0e7d-42ec-a592-8bc1ea4ea87d'
12
+ VERSION_BANDS = [
13
+ ['6.0.0', '7.0.0', '6.10.8'],
14
+ ['7.0.0', '7.17.0', '7.5.0'],
15
+ ['7.17.0', '8.0.0', '7.17.0'],
16
+ ['9.0.0', '10.0.0', '9.6.1.29396'],
17
+ ['10.0.0', '11.0.0', '10.24.0.73019'],
18
+ ['11.0.0', '12.0.0', '11.12.1']
19
+ ].map { |minimum, maximum, seed| [Gem::Version.new(minimum), Gem::Version.new(maximum), seed] }
20
+ .freeze
21
+
22
+ def self.for(version)
23
+ seed_version = seed_version_for(version)
24
+ path = File.join(__dir__, 'schemas', "system-model-#{seed_version}.b64")
25
+ raise CompilationError, "no native System model seed for Mendix #{version}" unless File.file?(path)
26
+
27
+ bytes = Zlib.gunzip(Base64.decode64(File.read(path)))
28
+ new(ModelPackage.decode(bytes, File.basename(path)), target_version: version, seed_version:)
29
+ rescue Zlib::Error, ArgumentError => e
30
+ raise CompilationError, "invalid System model seed for Mendix #{version}: #{e.message}"
31
+ end
32
+
33
+ def self.seed_version_for(version) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
34
+ exact = version.to_s
35
+ return exact if File.file?(File.join(__dir__, 'schemas', "system-model-#{exact}.b64"))
36
+ unless exact.match?(/\A\d+(?:\.\d+){1,3}(?:[-+].*)?\z/)
37
+ raise CompilationError,
38
+ "invalid Mendix version #{version.inspect}"
39
+ end
40
+
41
+ target = Gem::Version.new(exact.scan(/\d+/).first(3).join('.'))
42
+ band = VERSION_BANDS.find { |minimum, maximum, _seed| target >= minimum && target < maximum }
43
+ return band.last if band
44
+
45
+ raise CompilationError,
46
+ "no audited native System model seed for Mendix #{version}; " \
47
+ 'supported families are 6.x, 7.x, 9.x, 10.x, and 11.x'
48
+ rescue ArgumentError
49
+ raise CompilationError, "invalid Mendix version #{version.inspect}"
50
+ end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
51
+
52
+ attr_reader :package, :target_version, :seed_version
53
+
54
+ def initialize(package, target_version: nil, seed_version: nil)
55
+ @package = package
56
+ @target_version = target_version&.to_s
57
+ @seed_version = seed_version&.to_s
58
+ end
59
+
60
+ def module_reference
61
+ module_document = package.find(MODULE_ID)&.document
62
+ domain = domain_document
63
+ validate_module!(module_document, domain)
64
+
65
+ {
66
+ '$ID' => module_document['$ID'], '$Type' => module_document['$Type'],
67
+ 'DomainModel' => domain.slice('$ID', '$Type'),
68
+ 'AllDocuments' => document_references(module_document, domain)
69
+ }
70
+ end
71
+
72
+ def domain_document
73
+ package.documents.find { _1['$Type'] == 'DomainModels$DomainModel' }
74
+ end
75
+
76
+ def document_references(module_document, domain)
77
+ documents = package.documents.reject { [module_document, domain].include?(_1) }
78
+ documents.map { _1.slice('$ID', '$Type') }
79
+ end
80
+
81
+ def validate_module!(module_document, domain)
82
+ return if module_document && domain
83
+
84
+ raise CompilationError, 'System model seed is missing its module or domain model'
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ SystemQueueMaterialization = Data.define(:model_path, :queues)
6
+
7
+ # Adds the Runtime's built-in System workflow queues absent from editor MPRs.
8
+ class SystemQueueMaterializer
9
+ QUEUES = [
10
+ %w[cc8b48dd-2c7e-4aa0-a614-3c683349cb72 MendixWorkflows-WorkflowExecution 5
11
+ 3ba681ba-b94c-5f4a-82cb-d9e75947cb18],
12
+ %w[9d554d4e-7cf8-4a35-9504-a9f600f28e41 MendixWorkflows-DefaultTaskExecution 3
13
+ b06495bf-e4c6-5e62-8e98-56b7a852f440]
14
+ ].freeze
15
+
16
+ def initialize(mpr_path, deployment:)
17
+ @version = SourceModel.read(mpr_path).version
18
+ @deployment = File.expand_path(deployment)
19
+ end
20
+
21
+ def materialize # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
22
+ path = File.join(@deployment, 'model', 'model.mdp')
23
+ package = QUEUES.reduce(ModelPackage.read(path)) do |model, queue|
24
+ existing = model.documents.find { _1['QualifiedName'] == "System.#{queue[1]}" }
25
+ ids = if existing
26
+ [existing['$ID'], existing.dig('Config', '$ID')]
27
+ else
28
+ [queue[0], queue[3]]
29
+ end
30
+ model.upsert(document(ids[0], queue[1], queue[2], ids[1]))
31
+ end
32
+ package.write(path)
33
+ SystemQueueMaterialization.new(model_path: path, queues: QUEUES.length)
34
+ end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
35
+
36
+ private
37
+
38
+ def document(id, name, parallelism, config_id)
39
+ {
40
+ '$ID' => id, '$Type' => 'Queues$Queue',
41
+ 'Config' => queue_config(config_id, parallelism),
42
+ 'Name' => name, 'QualifiedName' => "System.#{name}"
43
+ }
44
+ end
45
+
46
+ def queue_config(id, parallelism)
47
+ config = { '$ID' => id, '$Type' => 'Queues$BasicQueueConfig' }
48
+ return config.merge('Parallelism' => parallelism.to_i) if @version.to_i < 10
49
+
50
+ config.merge('ParallelismExpression' => parallelism, 'ClusterWide' => false)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ SystemTextMaterialization = Data.define(:model_path, :texts)
6
+
7
+ # Stores the Runtime system-text index; localized values live in i18n properties.
8
+ class SystemTextMaterializer
9
+ include ModelValues
10
+
11
+ def initialize(mpr_path, deployment:)
12
+ @mpr_path = File.expand_path(mpr_path)
13
+ @deployment = File.expand_path(deployment)
14
+ end
15
+
16
+ def materialize
17
+ source = SourceModel.read(@mpr_path).units_of('Texts$SystemTextCollection').first
18
+ document = source ? compile(source.document) : empty_collection
19
+ path = File.join(@deployment, 'model', 'model.mdp')
20
+ ModelPackage.read(path).upsert(document).write(path)
21
+ SystemTextMaterialization.new(model_path: path, texts: document.fetch('SystemTexts').length)
22
+ end
23
+
24
+ private
25
+
26
+ def compile(source)
27
+ {
28
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
29
+ 'SystemTexts' => array(source['SystemTexts']).map { compile_text(_1) }
30
+ }
31
+ end
32
+
33
+ def empty_collection
34
+ {
35
+ '$ID' => '57d2ecb5-6c2f-4ffa-a0b3-a37a40da3adf',
36
+ '$Type' => 'Texts$SystemTextCollection', 'SystemTexts' => []
37
+ }
38
+ end
39
+
40
+ def compile_text(source)
41
+ {
42
+ '$ID' => source['$ID'], '$Type' => source['$Type'],
43
+ 'Text' => source.fetch('Text').slice('$ID', '$Type'),
44
+ 'InternalKey' => source['InternalKey']
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Mxrb
6
+ module Compiler
7
+ TranslationMaterialization = Data.define(:directory, :languages, :strings, :files)
8
+
9
+ # Synchronizes all MPR Texts$Text values into Runtime Java-properties catalogs.
10
+ class TranslationMaterializer
11
+ include ModelValues
12
+
13
+ def initialize(mpr_path, deployment:)
14
+ @mpr_path = File.expand_path(mpr_path)
15
+ @deployment = File.expand_path(deployment)
16
+ end
17
+
18
+ def materialize
19
+ translations = collect(SourceModel.read(@mpr_path))
20
+ directory = File.join(@deployment, 'model', 'i18n')
21
+ FileUtils.mkdir_p(directory)
22
+ write_languages(directory, translations)
23
+ write_default(directory, translations)
24
+ result(directory, translations)
25
+ end
26
+
27
+ private
28
+
29
+ def collect(source)
30
+ result = Hash.new { |hash, language| hash[language] = {} }
31
+ source.documents.each { collect_value(_1, result) }
32
+ result
33
+ end
34
+
35
+ def collect_value(value, result)
36
+ case value
37
+ when Hash
38
+ collect_text(value, result) if value['$Type'] == 'Texts$Text'
39
+ value.each_value { collect_value(_1, result) }
40
+ when Array then array(value).each { collect_value(_1, result) }
41
+ end
42
+ end
43
+
44
+ def collect_text(text, result)
45
+ key = IO::BsonCodec.extract_id(text['$ID'])
46
+ return unless key
47
+
48
+ array(text['Items']).each do |translation|
49
+ language = translation['LanguageCode'].to_s
50
+ result[language][key] = translation['Text'].to_s unless language.empty?
51
+ end
52
+ end
53
+
54
+ def update(path, values)
55
+ lines = File.file?(path) ? File.readlines(path, chomp: true) : []
56
+ seen = {}
57
+ rendered = lines.map { replace_line(_1, values, seen) }
58
+ values.keys.sort.each { |key| rendered << property(key, values.fetch(key)) unless seen[key] }
59
+ File.write(path, "#{rendered.join("\n")}\n")
60
+ end
61
+
62
+ def replace_line(line, values, seen)
63
+ key = line[/\A([0-9a-f-]{36})=/i, 1]
64
+ return line unless key && values.key?(key)
65
+
66
+ seen[key] = true
67
+ property(key, values.fetch(key))
68
+ end
69
+
70
+ def property(key, value)
71
+ escaped = value.to_s.gsub('\\', '\\\\').gsub(/\r\n|\r|\n/, '\\n')
72
+ .gsub(/([:=#!])/) { |character| "\\#{character}" }
73
+ "#{key}=#{escaped}"
74
+ end
75
+
76
+ def write_languages(directory, translations)
77
+ translations.each do |language, values|
78
+ update(File.join(directory, "translations_#{language}.properties"), values)
79
+ end
80
+ end
81
+
82
+ def write_default(directory, translations)
83
+ language = translations.key?('en_US') ? 'en_US' : translations.keys.min
84
+ update(File.join(directory, 'translations.properties'), translations.fetch(language, {}))
85
+ end
86
+
87
+ def result(directory, translations)
88
+ TranslationMaterialization.new(
89
+ directory:, languages: translations.keys.sort.freeze,
90
+ strings: translations.values.sum(&:length), files: translations.length + 1
91
+ )
92
+ end
93
+ end
94
+ end
95
+ end