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,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'digest'
5
+
6
+ module Mxrb
7
+ module Compiler
8
+ # Completes web templates normally hydrated by Studio Pro's proprietary build stage.
9
+ class WebShellMaterializer # rubocop:disable Metrics/ClassLength
10
+ CACHE_REVISION = 6
11
+ PLACEHOLDER = /\{\{[a-z0-9_-]+\}\}/i
12
+ DYNAMIC_PAGE_CACHE = /
13
+ (?:\(0,[A-Za-z0-9$_.]+\)\(\))\.getConfig\("isDevModeEnabled"\)\?"":
14
+ `\?\$\{((?:\(0,[A-Za-z0-9$_.]+\)\(\))\.getConfig\("cachebust"\))\}`
15
+ /x
16
+ SELF_IMPORT = %r{import\*as\s+(?<binding>[A-Za-z_$][A-Za-z0-9_$]*)\s+from"\./(?<asset>[A-Za-z0-9_.-]+\.js)";
17
+ e\.C\(\k<binding>\),}x
18
+
19
+ def initialize(web, version:)
20
+ @web = File.expand_path(web)
21
+ @version = version.to_s
22
+ end
23
+
24
+ def materialize
25
+ return 0 unless File.directory?(@web)
26
+
27
+ changed = html_files.count { render_html(_1) }
28
+ changed += materialize_dynamic_imports
29
+ write_missing(File.join(@web, 'js', 'login_i18n.js'), login_i18n)
30
+ write_missing(File.join(@web, 'lib', 'bootstrap', 'css', 'bootstrap.min.css'), login_styles)
31
+ # React Client requests this conventional aggregate even when every widget
32
+ # ships its styles through JavaScript or the compiled theme.
33
+ write_missing(File.join(@web, 'dist', 'widgets.css'), '')
34
+ changed
35
+ end
36
+
37
+ # React Client deliberately omits page cache tokens in developer mode. Native mxrb
38
+ # rebuilds can then leave an already-open browser on an obsolete generated page.
39
+ def materialize_dynamic_imports
40
+ Dir.glob(File.join(@web, 'dist', '**', '*.js')).sort.count do |path|
41
+ source = File.binread(path)
42
+ rendered = render_javascript(source)
43
+ next false if source == rendered
44
+
45
+ write_versioned_chunk(path, rendered)
46
+ true
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def html_files = Dir.glob(File.join(@web, '*.html')).sort
53
+
54
+ def render_javascript(source)
55
+ rendered = source.gsub(DYNAMIC_PAGE_CACHE) do
56
+ %(`?#{cache_token}\${#{Regexp.last_match(1)}}`)
57
+ end
58
+ rendered.gsub(SELF_IMPORT) do |match|
59
+ asset = Regexp.last_match(:asset)
60
+ match.sub(%("./#{asset}"), %("./#{asset}?#{cache_token}"))
61
+ end
62
+ end
63
+
64
+ def render_html(path)
65
+ source = File.read(path)
66
+ rendered = source.gsub('{{cachebust}}', cache_token)
67
+ rendered = rendered.gsub('{{unsupportedbrowser}}', unsupported_browser)
68
+ rendered = rendered.gsub('{{themecss}}', theme_css)
69
+ rendered = rendered.gsub('{{manifest}}', manifest)
70
+ rendered = rendered.gsub(PLACEHOLDER, '')
71
+ rendered = inject_navigation_compatibility(rendered) if File.basename(path) == 'index.html'
72
+ return false if source == rendered
73
+
74
+ File.write(path, rendered)
75
+ true
76
+ end
77
+
78
+ # Runtime's manifest handler accepts an opaque decimal cache token only.
79
+ def cache_token
80
+ Digest::SHA256.hexdigest("mxrb:web-shell:#{CACHE_REVISION}:#{@version}")[0, 15].to_i(16).to_s
81
+ end
82
+
83
+ def write_versioned_chunk(path, rendered)
84
+ old_stem = File.basename(path, '.js')
85
+ return File.binwrite(path, rendered) unless old_stem.match?(/\A[0-9a-f]{16}\z/)
86
+
87
+ new_stem = Digest::SHA256.hexdigest(rendered)[0, 16]
88
+ rewrite_chunk_references(old_stem, new_stem)
89
+ destination = File.join(File.dirname(path), "#{new_stem}.js")
90
+ File.binwrite(destination, rendered)
91
+ FileUtils.rm_f(path) unless destination == path
92
+ end
93
+
94
+ def rewrite_chunk_references(old_stem, new_stem)
95
+ Dir.glob(File.join(@web, 'dist', '**', '*.js')).sort.each do |path|
96
+ source = File.binread(path)
97
+ rendered = source.gsub(old_stem, new_stem)
98
+ File.binwrite(path, rendered) unless source == rendered
99
+ end
100
+ end
101
+
102
+ def theme_css
103
+ return '' unless File.file?(File.join(@web, 'theme.compiled.css'))
104
+
105
+ %(<link rel="stylesheet" href="theme.compiled.css?#{cache_token}">)
106
+ end
107
+
108
+ def manifest
109
+ %(<link rel="manifest" href="manifest.webmanifest?#{cache_token}" crossorigin="use-credentials">)
110
+ end
111
+
112
+ def inject_navigation_compatibility(html)
113
+ return html if html.include?('data-mxrb-navigation-compat') || !html.include?('</head>')
114
+
115
+ html.sub('</head>', "#{navigation_compatibility}\n</head>")
116
+ end
117
+
118
+ def navigation_compatibility # rubocop:disable Metrics/MethodLength
119
+ <<~HTML.chomp
120
+ <script data-mxrb-navigation-compat="#{CACHE_REVISION}">
121
+ (() => {
122
+ const install = () => {
123
+ const ui = window.mx && window.mx.ui;
124
+ if (!ui || typeof ui.openForm2 !== "function") return false;
125
+ if (typeof ui.openForm !== "function") {
126
+ ui.openForm = (page, options, onLoad, onError) =>
127
+ Promise.resolve(ui.openForm2(
128
+ page, {}, undefined, undefined, options || { location: "content" }
129
+ )).then(onLoad, onError);
130
+ }
131
+ return true;
132
+ };
133
+ if (!install()) {
134
+ const timer = window.setInterval(() => install() && window.clearInterval(timer), 50);
135
+ window.setTimeout(() => window.clearInterval(timer), 60000);
136
+ }
137
+ })();
138
+ </script>
139
+ HTML
140
+ end # rubocop:enable Metrics/MethodLength
141
+
142
+ def unsupported_browser
143
+ <<~HTML.chomp
144
+ <script type="text/javascript">
145
+ try { eval("async () => {}"); }
146
+ catch (error) {
147
+ var homeUrl = window.location.origin + window.location.pathname;
148
+ var appUrl = homeUrl.slice(0, homeUrl.lastIndexOf("/") + 1);
149
+ window.location.replace(appUrl + "unsupported-browser.html");
150
+ }
151
+ </script>
152
+ HTML
153
+ end
154
+
155
+ def write_missing(path, contents)
156
+ return if File.exist?(path)
157
+
158
+ FileUtils.mkdir_p(File.dirname(path))
159
+ File.write(path, contents)
160
+ end
161
+
162
+ def login_i18n
163
+ <<~JS
164
+ window.i18nMap = {
165
+ loginHeader: "Sign in", username: "User name", password: "Password",
166
+ loginButton: "Sign in", goHomeButton: "Go home",
167
+ http401: "The user name or password is incorrect.",
168
+ http402: "The application license does not permit this sign-in.",
169
+ http403: "Access is denied.", http404: "The application is unavailable.",
170
+ http500: "The application encountered an error.",
171
+ http503: "The application is starting. Try again shortly.",
172
+ httpdefault: "Sign-in failed. Try again."
173
+ };
174
+ JS
175
+ end
176
+
177
+ def login_styles
178
+ <<~CSS
179
+ *{box-sizing:border-box}html,body{margin:0;font-family:Inter,Arial,sans-serif;color:#17212b}
180
+ body{background:linear-gradient(135deg,#f4f8fb,#e6f1f6)}
181
+ .form-group{margin-bottom:16px}.form-control{display:block;width:100%;height:42px;padding:10px 12px;
182
+ border:1px solid #b8c5cf;border-radius:6px;background:#fff;font-size:14px;box-shadow:inset 0 1px 2px #0000000d}
183
+ .form-control:focus{border-color:#0595db;outline:0;box-shadow:0 0 0 3px #0595db26}
184
+ .btn{display:inline-block;padding:10px 18px;border:0;border-radius:6px;font-weight:600;cursor:pointer}
185
+ .btn-primary{width:100%;color:#fff;background:#0595db}.btn-primary:hover{background:#087fb8}
186
+ .btn[disabled]{cursor:not-allowed;opacity:.6}.alert{padding:10px 12px;margin-bottom:16px;border-radius:6px}
187
+ .alert-danger{color:#842029;background:#f8d7da;border:1px solid #f5c2c7}
188
+ .login-container{filter:drop-shadow(0 18px 35px #1c526626)}
189
+ .login-form{background:#fff;border-radius:12px}.login-form label{display:block;margin-bottom:6px}
190
+ CSS
191
+ end
192
+ end # rubocop:enable Metrics/ClassLength
193
+ end
194
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'zip'
5
+
6
+ module Mxrb
7
+ module Compiler
8
+ # Expands project-owned pluggable widget packages into the web build tree.
9
+ # rubocop:disable Metrics
10
+ class WidgetPackageExtractor
11
+ def initialize(project_root, web_root)
12
+ @source = File.join(project_root, 'widgets')
13
+ @destination = File.join(web_root, 'widgets')
14
+ end
15
+
16
+ def extract
17
+ return 0 unless File.directory?(@source)
18
+
19
+ FileUtils.mkdir_p(@destination)
20
+ Dir.glob(File.join(@source, '*.mpk')).sort.sum { extract_package(_1) }
21
+ end
22
+
23
+ private
24
+
25
+ def extract_package(path)
26
+ Zip::File.open(path) do |archive|
27
+ entries = archive.reject(&:directory?)
28
+ entries.each { safe_destination(_1.name) }
29
+ runtime_roots = entries.select { _1.name.end_with?('.mjs') }
30
+ .map { File.dirname(_1.name) }.uniq
31
+ runtime_entries = entries.select do |entry|
32
+ runtime_roots.any? do |root|
33
+ if root == '.'
34
+ entry.name.match?(/\.(?:m?js|css)\z/) || entry.name.start_with?('assets/')
35
+ else
36
+ entry.name.start_with?("#{root}/")
37
+ end
38
+ end
39
+ end
40
+ runtime_entries.each { write_entry(_1) }
41
+ return runtime_entries.length
42
+ end
43
+ rescue Zip::Error => e
44
+ raise CompilationError, "invalid widget package #{path}: #{e.message}"
45
+ end
46
+
47
+ def write_entry(entry)
48
+ destination = safe_destination(entry.name)
49
+ FileUtils.mkdir_p(File.dirname(destination))
50
+ File.binwrite(destination, entry.get_input_stream.read)
51
+ end
52
+
53
+ def safe_destination(name)
54
+ destination = File.expand_path(name, @destination)
55
+ prefix = "#{File.expand_path(@destination)}#{File::SEPARATOR}"
56
+ return destination if destination.start_with?(prefix)
57
+
58
+ raise CompilationError, "unsafe widget package entry #{name}"
59
+ end
60
+ end
61
+ # rubocop:enable Metrics
62
+ end
63
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module Mxrb
6
+ DoctorCheck = Data.define(:name, :status, :message) do
7
+ def ok? = status == :ok
8
+ end
9
+ DoctorReport = Data.define(:root, :checks) do
10
+ def valid? = checks.all? { _1.status != :error }
11
+ def errors = checks.select { _1.status == :error }
12
+ def warnings = checks.select { _1.status == :warning }
13
+ end
14
+
15
+ # Diagnoses project structure and optional external toolchain availability.
16
+ class Doctor
17
+ def initialize(root = Dir.pwd, runner: Open3.method(:capture3))
18
+ expanded = File.expand_path(root)
19
+ @root = File.file?(expanded) ? File.dirname(expanded) : expanded
20
+ @runner = runner
21
+ end
22
+
23
+ def run
24
+ DoctorReport.new(@root, [
25
+ ruby_check, project_check, modules_check, aggregators_check,
26
+ mpr_check, executable_check('bundle'), executable_check('java'),
27
+ executable_check('docker', warning: true), runtime_check
28
+ ].freeze)
29
+ end
30
+
31
+ private
32
+
33
+ def ruby_check
34
+ status = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('4.0') ? :ok : :error
35
+ DoctorCheck.new(:ruby, status, "Ruby #{RUBY_VERSION}")
36
+ end
37
+
38
+ def project_check = file_check(:project, File.join(@root, 'project.rb'), 'project.rb')
39
+
40
+ def modules_check
41
+ modules = Dir[File.join(@root, 'modules', '*', 'module.rb')]
42
+ status = modules.empty? ? :error : :ok
43
+ DoctorCheck.new(:modules, status, "#{modules.size} module definition(s)")
44
+ end
45
+
46
+ def aggregators_check
47
+ files = Dir[File.join(@root, 'modules', '**', '*.rb')]
48
+ missing = files.select { File.read(_1).include?('evaluate_dir') }
49
+ .flat_map { missing_evaluate_dirs(_1) }
50
+ status = missing.empty? ? :ok : :warning
51
+ message = missing.empty? ? 'all referenced directories exist' : missing.join(', ')
52
+ DoctorCheck.new(:aggregators, status, message)
53
+ end
54
+
55
+ def missing_evaluate_dirs(path)
56
+ File.read(path).scan(/evaluate_dir File\.join\(__dir__,\s*"([^"]+)"\)/).filter_map do |match|
57
+ directory = File.join(File.dirname(path), match.first)
58
+ directory unless File.directory?(directory)
59
+ end
60
+ end
61
+
62
+ def mpr_check
63
+ path = Dir[File.join(@root, '*.mpr')].first
64
+ return DoctorCheck.new(:mpr, :warning, 'no generated MPR found') unless path
65
+
66
+ result = Mxrb.validate(path)
67
+ DoctorCheck.new(:mpr, result.valid? ? :ok : :error, "#{result.errors.size} validation error(s)")
68
+ rescue StandardError => e
69
+ DoctorCheck.new(:mpr, :error, e.message)
70
+ end
71
+
72
+ def executable_check(name, warning: false)
73
+ available = executable_candidates(name).any? { command_available?(_1) }
74
+ result = available ? :ok : unavailable_status(warning)
75
+ DoctorCheck.new(name.to_sym, result, available ? 'available' : 'not available')
76
+ end
77
+
78
+ def command_available?(command)
79
+ _output, _error, status = @runner.call(command, '--version')
80
+ status.success?
81
+ rescue Errno::ENOENT
82
+ false
83
+ end
84
+
85
+ def unavailable_status(warning) = warning ? :warning : :error
86
+
87
+ def executable_candidates(name)
88
+ return [name] unless name == 'java'
89
+
90
+ java_home = ENV['JAVA_HOME'].to_s
91
+ candidates = [java_home.empty? ? nil : File.join(java_home, 'bin', 'java'), name]
92
+ candidates.concat(Dir.glob(File.join(Dir.home, '.asdf', 'installs', 'java', '*', 'bin', 'java')).sort.reverse)
93
+ candidates.compact.uniq
94
+ end
95
+
96
+ def runtime_check
97
+ configured = ENV['MXRB_MENDIX_HOME'].to_s
98
+ roots = [configured, File.join(Dir.home, '.local', 'share', 'mendix')].reject(&:empty?)
99
+ candidates = roots.flat_map { runtime_candidates(_1) }.uniq
100
+ found = candidates.find do |root|
101
+ Runtime::RUNTIME_REQUIRED_FILES.all? { File.file?(File.join(root, _1)) }
102
+ end
103
+ DoctorCheck.new(:runtime, found ? :ok : :warning, found || 'not found')
104
+ end
105
+
106
+ def runtime_candidates(root)
107
+ direct = File.basename(root) == 'runtime' ? root : File.join(root, 'runtime')
108
+ [direct] + Dir.glob(File.join(root, '*', 'runtime'))
109
+ end
110
+
111
+ def file_check(name, path, label)
112
+ DoctorCheck.new(name, File.file?(path) ? :ok : :error,
113
+ File.file?(path) ? "#{label} found" : "#{label} missing")
114
+ end
115
+ end
116
+ end