mxrb 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mxrb +458 -10
  3. data/docs/de-DE/README.md +1 -0
  4. data/docs/de-DE/oql-sql.md +42 -0
  5. data/docs/de-DE/project-structure.md +201 -2
  6. data/docs/de-DE/runtime-ruby.md +104 -0
  7. data/docs/de-DE/runtime-schema-migrations.md +44 -0
  8. data/docs/de-DE/validation-matrix.md +42 -4
  9. data/docs/en-US/README.md +1 -0
  10. data/docs/en-US/oql-sql.md +41 -0
  11. data/docs/en-US/project-structure.md +192 -2
  12. data/docs/en-US/runtime-ruby.md +100 -0
  13. data/docs/en-US/runtime-schema-migrations.md +41 -0
  14. data/docs/en-US/validation-matrix.md +39 -4
  15. data/docs/pt-BR/README.md +1 -0
  16. data/docs/pt-BR/entity-dsl.md +8 -0
  17. data/docs/pt-BR/oql-sql.md +41 -0
  18. data/docs/pt-BR/project-structure.md +197 -2
  19. data/docs/pt-BR/runtime-ruby.md +108 -0
  20. data/docs/pt-BR/runtime-schema-migrations.md +40 -0
  21. data/docs/pt-BR/validation-matrix.md +42 -5
  22. data/docs/pt-BR/writing.md +28 -0
  23. data/lib/mxrb/cli/help.rb +426 -0
  24. data/lib/mxrb/cli/release.rb +137 -0
  25. data/lib/mxrb/compiler/compatibility_analyzer.rb +1 -1
  26. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +8 -1
  27. data/lib/mxrb/compiler/domain_document_compiler.rb +3 -0
  28. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +11 -1
  29. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +76 -19
  30. data/lib/mxrb/compiler/legacy_custom_widget_compiler.rb +159 -0
  31. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +43 -7
  32. data/lib/mxrb/compiler/legacy_page_builder.rb +1248 -23
  33. data/lib/mxrb/compiler/packager.rb +27 -13
  34. data/lib/mxrb/compiler/page_bundle_compiler.rb +13 -4
  35. data/lib/mxrb/compiler/portable_packager.rb +16 -11
  36. data/lib/mxrb/compiler/web_bundle_builder.rb +5 -1
  37. data/lib/mxrb/compiler/web_shell_materializer.rb +42 -2
  38. data/lib/mxrb/compiler/widget_package_extractor.rb +3 -0
  39. data/lib/mxrb/domain_diagram/lifecycle.rb +272 -0
  40. data/lib/mxrb/domain_diagram.rb +409 -0
  41. data/lib/mxrb/dsl/builder.rb +62 -11
  42. data/lib/mxrb/environment.rb +226 -0
  43. data/lib/mxrb/exporter.rb +265 -45
  44. data/lib/mxrb/http/server.rb +107 -0
  45. data/lib/mxrb/initializer.rb +80 -9
  46. data/lib/mxrb/integrity/validator.rb +32 -7
  47. data/lib/mxrb/io/bson_codec.rb +42 -1
  48. data/lib/mxrb/io/mpr_file.rb +146 -3
  49. data/lib/mxrb/model/attribute.rb +3 -0
  50. data/lib/mxrb/model/entity.rb +44 -2
  51. data/lib/mxrb/model/microflow.rb +3 -1
  52. data/lib/mxrb/model/module.rb +71 -0
  53. data/lib/mxrb/model/page.rb +126 -15
  54. data/lib/mxrb/model/project.rb +3 -0
  55. data/lib/mxrb/modeler/catalog.rb +178 -0
  56. data/lib/mxrb/official_marketplace/module_package_importer.rb +23 -12
  57. data/lib/mxrb/official_marketplace/widget_package_installer.rb +15 -11
  58. data/lib/mxrb/official_marketplace.rb +9 -3
  59. data/lib/mxrb/oql/reverse_translator.rb +394 -0
  60. data/lib/mxrb/oql/server.rb +4 -8
  61. data/lib/mxrb/oql.rb +32 -4
  62. data/lib/mxrb/progress.rb +242 -0
  63. data/lib/mxrb/project_lifecycle.rb +5 -2
  64. data/lib/mxrb/ruby_app/exporter.rb +1749 -0
  65. data/lib/mxrb/ruby_app/preset.rb +506 -0
  66. data/lib/mxrb/ruby_app/session_manager.rb +122 -0
  67. data/lib/mxrb/ruby_app.rb +1361 -0
  68. data/lib/mxrb/runtime/access_control.rb +408 -0
  69. data/lib/mxrb/runtime/native.rb +1015 -93
  70. data/lib/mxrb/runtime/scheduler.rb +396 -0
  71. data/lib/mxrb/runtime/schema_migrator.rb +598 -0
  72. data/lib/mxrb/runtime/shared_store.rb +335 -0
  73. data/lib/mxrb/runtime/sqlite_store.rb +729 -0
  74. data/lib/mxrb/scaffold/generator.rb +11 -3
  75. data/lib/mxrb/team_server.rb +10 -7
  76. data/lib/mxrb/uml/activity_diagram.rb +118 -0
  77. data/lib/mxrb/uml/class_diagram.rb +118 -0
  78. data/lib/mxrb/uml/sequence_diagram.rb +112 -0
  79. data/lib/mxrb/uml/server.rb +146 -0
  80. data/lib/mxrb/uml/support.rb +35 -0
  81. data/lib/mxrb/version.rb +1 -1
  82. data/lib/mxrb/web_ui/assets/abnfDiagram-N423BO3Z-C4JD3cEC.js +1 -0
  83. data/lib/mxrb/web_ui/assets/arc-3Z53kgFp.js +1 -0
  84. data/lib/mxrb/web_ui/assets/architecture-TIHT7OUA-BStBTLea.js +1 -0
  85. data/lib/mxrb/web_ui/assets/architectureDiagram-T3A2C74G-DBwqnM4J.js +36 -0
  86. data/lib/mxrb/web_ui/assets/array-BifhSqXX.js +1 -0
  87. data/lib/mxrb/web_ui/assets/blockDiagram-VBNYF7ZC-DLtuHt-j.js +132 -0
  88. data/lib/mxrb/web_ui/assets/c4Diagram-5PPSVZJV-ConSk_pe.js +10 -0
  89. data/lib/mxrb/web_ui/assets/channel-BhPvD7Dt.js +1 -0
  90. data/lib/mxrb/web_ui/assets/chunk-2GRJ4B5K-D2HpWsf_.js +1 -0
  91. data/lib/mxrb/web_ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  92. data/lib/mxrb/web_ui/assets/chunk-4I5QYGJK-CiIdNwaF.js +1 -0
  93. data/lib/mxrb/web_ui/assets/chunk-5RXB4S5H-Bu_AaEkE.js +231 -0
  94. data/lib/mxrb/web_ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  95. data/lib/mxrb/web_ui/assets/chunk-6Q2QTUOP-wQkNmt_X.js +88 -0
  96. data/lib/mxrb/web_ui/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
  97. data/lib/mxrb/web_ui/assets/chunk-GF5L2VYU-G8MmiBzU.js +206 -0
  98. data/lib/mxrb/web_ui/assets/chunk-I66GZJ75-CulcrLPh.js +127 -0
  99. data/lib/mxrb/web_ui/assets/chunk-JQJVKLGR-ukjzcdlL.js +156 -0
  100. data/lib/mxrb/web_ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  101. data/lib/mxrb/web_ui/assets/chunk-KBJHAD2P-Wm35lYov.js +1 -0
  102. data/lib/mxrb/web_ui/assets/chunk-KEIR6QF5-BICK3FdT.js +161 -0
  103. data/lib/mxrb/web_ui/assets/chunk-NSK5VX7P-D2nYrCY4.js +2 -0
  104. data/lib/mxrb/web_ui/assets/chunk-QR6OTTB3-BrdIzg4Q.js +62 -0
  105. data/lib/mxrb/web_ui/assets/chunk-RYQCIY6F-CCfZMcW6.js +1 -0
  106. data/lib/mxrb/web_ui/assets/chunk-UBXNYLIW-BPEVYsFK.js +1 -0
  107. data/lib/mxrb/web_ui/assets/chunk-W5SLKNZC-2E6OPhA4.js +1 -0
  108. data/lib/mxrb/web_ui/assets/chunk-WRU74C26-CBIvOyWp.js +70 -0
  109. data/lib/mxrb/web_ui/assets/chunk-XXDRQBXY-pH58XAyl.js +1 -0
  110. data/lib/mxrb/web_ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  111. data/lib/mxrb/web_ui/assets/classDiagram-JCYQIIEL-xiqo4D89.js +1 -0
  112. data/lib/mxrb/web_ui/assets/classDiagram-v2-OCEON4UE-xiqo4D89.js +1 -0
  113. data/lib/mxrb/web_ui/assets/cose-bilkent-JH36ORCC-t_flPpii.js +1 -0
  114. data/lib/mxrb/web_ui/assets/cynefin-VYW2F7L2-BIqktTEv.js +1 -0
  115. data/lib/mxrb/web_ui/assets/cynefinDiagram-MW4NZA55-C2d--lut.js +62 -0
  116. data/lib/mxrb/web_ui/assets/cytoscape.esm-B-NFISlW.js +321 -0
  117. data/lib/mxrb/web_ui/assets/dagre-Buvkdvvj.js +1 -0
  118. data/lib/mxrb/web_ui/assets/dagre-VZM6K2ZE-CSz2_tb5.js +4 -0
  119. data/lib/mxrb/web_ui/assets/defaultLocale-BFoDCU3G.js +1 -0
  120. data/lib/mxrb/web_ui/assets/diagram-7IWD3JNH-DCt7u7V_.js +30 -0
  121. data/lib/mxrb/web_ui/assets/diagram-B4RE2ZJO-DqZBqkMu.js +3 -0
  122. data/lib/mxrb/web_ui/assets/diagram-LBJQPF4R-ClIYbIsI.js +24 -0
  123. data/lib/mxrb/web_ui/assets/diagram-Q27KOJAE-DFxhvthJ.js +24 -0
  124. data/lib/mxrb/web_ui/assets/diagram-UB23O5K3-BSUTXptv.js +41 -0
  125. data/lib/mxrb/web_ui/assets/dist-D2qOEbeJ.js +1 -0
  126. data/lib/mxrb/web_ui/assets/domain-mGOZ1KCG.css +1 -0
  127. data/lib/mxrb/web_ui/assets/domain-qsjXhy4v.js +1 -0
  128. data/lib/mxrb/web_ui/assets/ebnfDiagram-BXEA7PRR-B5reICEi.js +1 -0
  129. data/lib/mxrb/web_ui/assets/erDiagram-JOGREHBK-Co5Mxn2p.js +85 -0
  130. data/lib/mxrb/web_ui/assets/eventmodeling-45OFAUF4-C1ylhSks.js +1 -0
  131. data/lib/mxrb/web_ui/assets/flowDiagram-UKHOOZJN-CHXaJIaS.js +1 -0
  132. data/lib/mxrb/web_ui/assets/ganttDiagram-PKOTCBZU-DPPPaWaz.js +292 -0
  133. data/lib/mxrb/web_ui/assets/gitGraph-TEB2WS4Q-qS77ywA9.js +1 -0
  134. data/lib/mxrb/web_ui/assets/gitGraphDiagram-DS77QQ5N-DpAv6-BT.js +106 -0
  135. data/lib/mxrb/web_ui/assets/graphlib-DS17s2tU.js +1 -0
  136. data/lib/mxrb/web_ui/assets/info-DKCQHKI2-C9VD1Y4e.js +1 -0
  137. data/lib/mxrb/web_ui/assets/infoDiagram-6WML65LV-Bk1kbcCy.js +2 -0
  138. data/lib/mxrb/web_ui/assets/init-C-OQMol4.js +1 -0
  139. data/lib/mxrb/web_ui/assets/ishikawaDiagram-WSZJBQD7-0gcISBFR.js +70 -0
  140. data/lib/mxrb/web_ui/assets/journeyDiagram-NVQOT4AX-Dt0RQnlh.js +139 -0
  141. data/lib/mxrb/web_ui/assets/jsx-runtime-DV0a5kSb.js +9 -0
  142. data/lib/mxrb/web_ui/assets/kanban-definition-27J2QSJJ-DIPQM7w9.js +89 -0
  143. data/lib/mxrb/web_ui/assets/katex-CXMH3UgJ.js +257 -0
  144. data/lib/mxrb/web_ui/assets/line-C0rYD-tL.js +1 -0
  145. data/lib/mxrb/web_ui/assets/linear-BPPxWorj.js +1 -0
  146. data/lib/mxrb/web_ui/assets/map-BaFkSB1l.js +1 -0
  147. data/lib/mxrb/web_ui/assets/mermaid-parser.core-B_UPzTxa.js +7 -0
  148. data/lib/mxrb/web_ui/assets/mindmap-definition-FAOFIHXS-CdJRSMuk.js +96 -0
  149. data/lib/mxrb/web_ui/assets/modeler-BNqHthwk.css +1 -0
  150. data/lib/mxrb/web_ui/assets/modeler-CQmqflbg.js +1 -0
  151. data/lib/mxrb/web_ui/assets/ordinal-BDEzSJ7C.js +1 -0
  152. data/lib/mxrb/web_ui/assets/packet-7NZHBO7P-CwQ4NSBs.js +1 -0
  153. data/lib/mxrb/web_ui/assets/path-fybaL0A-.js +1 -0
  154. data/lib/mxrb/web_ui/assets/pegDiagram-VL7TDLO6-D40S7ppx.js +1 -0
  155. data/lib/mxrb/web_ui/assets/pie-RZYD4A2V-CJroHMKh.js +1 -0
  156. data/lib/mxrb/web_ui/assets/pieDiagram-7S7Q4E2Y-QB54__BV.js +39 -0
  157. data/lib/mxrb/web_ui/assets/quadrantDiagram-CIZ2JOQS-DBhDS6ag.js +7 -0
  158. data/lib/mxrb/web_ui/assets/radar-I7S5WNFK-DdIUwd2d.js +1 -0
  159. data/lib/mxrb/web_ui/assets/railroad-3IZDKUUU-B1Lw6Xyq.js +1 -0
  160. data/lib/mxrb/web_ui/assets/railroad-abnf-AHOZXSZD-CQgQoeak.js +1 -0
  161. data/lib/mxrb/web_ui/assets/railroad-ebnf-EBAXGLYW-D7qEQ2x8.js +1 -0
  162. data/lib/mxrb/web_ui/assets/railroad-peg-LSFZ7HO6-C005Vxbb.js +1 -0
  163. data/lib/mxrb/web_ui/assets/railroadDiagram-AXF67PYL-BBP2qk7M.js +1 -0
  164. data/lib/mxrb/web_ui/assets/requirementDiagram-LRYGKXZP-D2cLsAoI.js +84 -0
  165. data/lib/mxrb/web_ui/assets/rough.esm-Dy-Kn_BL.js +1 -0
  166. data/lib/mxrb/web_ui/assets/sankeyDiagram-W5VNT64P-Bpi3FiP2.js +40 -0
  167. data/lib/mxrb/web_ui/assets/sequenceDiagram-SI44F4Z6-DCRcDlVe.js +162 -0
  168. data/lib/mxrb/web_ui/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
  169. data/lib/mxrb/web_ui/assets/src-CvxfUak2.js +1 -0
  170. data/lib/mxrb/web_ui/assets/stateDiagram-OKZ733FA-C2GCEoxl.js +1 -0
  171. data/lib/mxrb/web_ui/assets/stateDiagram-v2-UEYNNEHI-D2gWgedh.js +1 -0
  172. data/lib/mxrb/web_ui/assets/swimlanes-SLNWSIFB-CRI9irEW.js +2 -0
  173. data/lib/mxrb/web_ui/assets/swimlanesDiagram-ULZ7WXOC-C5Wb_-hZ.js +8 -0
  174. data/lib/mxrb/web_ui/assets/theme-_mxc7BdY.css +1 -0
  175. data/lib/mxrb/web_ui/assets/timeline-definition-Z64GVDOM-B1-mNg-h.js +120 -0
  176. data/lib/mxrb/web_ui/assets/treeView-QDETBFTQ-7CTvIwKq.js +1 -0
  177. data/lib/mxrb/web_ui/assets/treemap-6X3UGDF4-CtL22pq5.js +1 -0
  178. data/lib/mxrb/web_ui/assets/uml-C3KAFGTq.js +51 -0
  179. data/lib/mxrb/web_ui/assets/uml-Dn-qd01X.css +1 -0
  180. data/lib/mxrb/web_ui/assets/vennDiagram-T6HMQDX7-BzVnUEAy.js +34 -0
  181. data/lib/mxrb/web_ui/assets/wardley-OPB4EBWU-JNi-QNag.js +1 -0
  182. data/lib/mxrb/web_ui/assets/wardleyDiagram-T6FBY63Y-BUwmAQ-B.js +78 -0
  183. data/lib/mxrb/web_ui/assets/xychartDiagram-ELKLHX3M-DEQS8sKp.js +7 -0
  184. data/lib/mxrb/web_ui/domain.html +15 -0
  185. data/lib/mxrb/web_ui/modeler.html +16 -0
  186. data/lib/mxrb/web_ui/uml.html +31 -0
  187. data/lib/mxrb/web_ui.rb +89 -0
  188. data/lib/mxrb/widget_certification.rb +208 -0
  189. data/lib/mxrb/writer.rb +278 -41
  190. data/lib/mxrb.rb +38 -8
  191. metadata +157 -5
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'json'
5
+ require 'tmpdir'
6
+ require 'zip'
7
+
8
+ module Mxrb
9
+ # Fail-closed evidence gate for core and pluggable widgets used by an MPR.
10
+ # rubocop:disable Metrics
11
+ class WidgetCertification
12
+ CORE_WIDGET_TYPES = %w[
13
+ Forms$DivContainer Forms$LayoutGrid Forms$LayoutGridRow Forms$LayoutGridColumn Forms$Table
14
+ Forms$DynamicText Forms$Title Forms$ActionButton Forms$DataView Forms$ListView Forms$TextBox
15
+ Forms$TextArea Forms$DatePicker Forms$CheckBox Forms$RadioButtonGroup Forms$FileManager
16
+ Forms$GroupBox Forms$SnippetCallWidget Forms$Label Forms$TabControl Forms$StaticImageViewer
17
+ Forms$ScrollContainer Forms$Placeholder Forms$SidebarToggleButton Forms$Header
18
+ Forms$NavigationTree Forms$MenuBar Forms$SimpleMenuBar
19
+ ].freeze
20
+ FAILURE_MARKERS = [
21
+ 'Could not render widget',
22
+ 'An error occurred, please contact your system administrator.',
23
+ 'Executing runtime operation failed for security reasons',
24
+ 'Out of context', 'out of context', 'must be placed inside',
25
+ 'should be placed within', 'failed to load'
26
+ ].freeze
27
+
28
+ def initialize(path, browser_report: nil, build: true, mendix_home: nil)
29
+ @path = File.expand_path(path)
30
+ @browser_report = browser_report && File.expand_path(browser_report)
31
+ @build = build
32
+ @mendix_home = mendix_home
33
+ end
34
+
35
+ def run
36
+ started = monotonic
37
+ source = Compiler::SourceModel.read(@path)
38
+ inventory = widget_inventory(source)
39
+ compilation = compile_pages(source)
40
+ package_inventory = packages(inventory.fetch(:custom).map { _1.fetch(:id) })
41
+ native_build = @build ? build_web(source.version) : { status: 'not_run' }
42
+ browser = browser_evidence(inventory)
43
+ failures = failures(compilation, package_inventory, native_build, browser)
44
+ {
45
+ path: @path, mendix_version: source.version,
46
+ status: failures.empty? ? 'pass' : 'fail', failures:,
47
+ inventory:, packages: package_inventory, compilation:, native_build:, browser:,
48
+ elapsed_seconds: (monotonic - started).round(3)
49
+ }
50
+ rescue StandardError => e
51
+ {
52
+ path: @path, status: 'fail', failures: [e.message],
53
+ error: { class: e.class.name, message: e.message },
54
+ elapsed_seconds: (monotonic - started).round(3)
55
+ }
56
+ end
57
+
58
+ private
59
+
60
+ def widget_inventory(source)
61
+ found = web_units(source).flat_map do |unit|
62
+ nested_widgets(unit.document).map do |widget|
63
+ custom = widget['$Type'] == 'CustomWidgets$CustomWidget'
64
+ {
65
+ page: "#{unit.module_name}.#{unit.document['Name']}", name: widget['Name'].to_s,
66
+ type: widget['$Type'].to_s, id: (custom ? custom_widget_id(source, widget) : nil)
67
+ }
68
+ end
69
+ end
70
+ core = found.reject { _1[:id] }.group_by { _1.fetch(:type) }.map do |type, entries|
71
+ { type:, occurrences: entries.length, pages: entries.map { _1.fetch(:page) }.uniq.sort }
72
+ end
73
+ core.sort_by! { _1.fetch(:type) }
74
+ custom = found.select { _1[:id] }.group_by { _1.fetch(:id) }.map do |id, entries|
75
+ { id:, occurrences: entries.length, pages: entries.map { _1.fetch(:page) }.uniq.sort }
76
+ end
77
+ custom.sort_by! { _1.fetch(:id) }
78
+ { total: found.length, core:, custom: }
79
+ end
80
+
81
+ def nested_widgets(value, found = [])
82
+ case value
83
+ when Hash
84
+ type = value['$Type'].to_s
85
+ found << value if CORE_WIDGET_TYPES.include?(type) || type == 'CustomWidgets$CustomWidget'
86
+ value.each_value { nested_widgets(_1, found) }
87
+ when Array then value.each { nested_widgets(_1, found) }
88
+ end
89
+ found
90
+ end
91
+
92
+ def custom_widget_id(source, widget)
93
+ direct = widget.dig('Type', 'WidgetId').to_s
94
+ return direct unless direct.empty?
95
+
96
+ pointer = IO::BsonCodec.extract_id(widget.dig('Object', 'TypePointer'))
97
+ source.document_index.values.find do |document|
98
+ IO::BsonCodec.extract_id(document.dig('ObjectType', '$ID')) == pointer
99
+ end&.fetch('WidgetId', '').to_s
100
+ end
101
+
102
+ def compile_pages(source)
103
+ pages = web_units(source).map do |unit|
104
+ bundle = if unit.document['$Type'] == 'Forms$Layout'
105
+ Compiler::PageBundleCompiler.new(source).compile_layout(unit)
106
+ else
107
+ Compiler::PageBundleCompiler.new(source).compile(unit)
108
+ end
109
+ {
110
+ name: bundle.qualified_name, bytes: bundle.source.bytesize,
111
+ unsupported: bundle.unsupported_widgets,
112
+ unsupported_custom: bundle.unsupported_custom_widgets
113
+ }
114
+ end
115
+ { status: pages.all? { _1[:unsupported].empty? && _1[:unsupported_custom].empty? } ? 'pass' : 'fail', pages: }
116
+ end
117
+
118
+ def packages(widget_ids)
119
+ paths = Dir.glob(File.join(File.dirname(@path), 'widgets', '*.mpk')).sort
120
+ entries = paths.map do |path|
121
+ modules = Zip::File.open(path) { _1.map(&:name).grep(/\.mjs\z/) }
122
+ ids = widget_ids.select { modules.include?("#{_1.tr('.', '/')}.mjs") }
123
+ next if ids.empty?
124
+
125
+ { file: File.basename(path), sha256: Digest::SHA256.file(path).hexdigest, widget_ids: ids.sort }
126
+ rescue Zip::Error => e
127
+ { file: File.basename(path), error: e.message, widget_ids: [] }
128
+ end.compact
129
+ covered = entries.flat_map { _1.fetch(:widget_ids) }.uniq
130
+ {
131
+ status: (widget_ids - covered).empty? ? 'pass' : 'fail', entries:,
132
+ missing_widget_ids: (widget_ids - covered).sort
133
+ }
134
+ end
135
+
136
+ def build_web(version)
137
+ root = @mendix_home || File.join(Dir.home, '.local', 'share', 'mendix', version)
138
+ started = monotonic
139
+ Dir.mktmpdir('mxrb-widget-certification-') do |directory|
140
+ deployment = File.join(directory, 'deployment')
141
+ stages = Compiler::DeploymentMaterializer.new(
142
+ @path, deployment:, mendix_home: root
143
+ ).materialize.stages.length
144
+ web = Compiler::WebBundleBuilder.new(
145
+ @path, deployment:, mendix_home: root
146
+ ).build
147
+ return {
148
+ status: 'pass', stages:, files: web.files, bytes: web.bytes,
149
+ elapsed_ms: ((monotonic - started) * 1000).round(1)
150
+ }
151
+ end
152
+ end
153
+
154
+ def browser_evidence(inventory)
155
+ required_ids = inventory.fetch(:custom).map { _1.fetch(:id) }
156
+ required_types = inventory.fetch(:core).map { _1.fetch(:type) }
157
+ return { status: 'missing', missing_widget_ids: required_ids, missing_widget_types: required_types } unless
158
+ @browser_report && File.file?(@browser_report)
159
+
160
+ report = JSON.parse(File.read(@browser_report))
161
+ declared = Array(report.dig('certification', 'widget_ids')).map(&:to_s).uniq
162
+ declared_types = Array(report.dig('certification', 'widget_types')).map(&:to_s).uniq
163
+ visible_failures = browser_failure_markers(report)
164
+ missing = required_ids - declared
165
+ missing_types = required_types - declared_types
166
+ status = if report['passed'] == true && visible_failures.empty? && missing.empty? && missing_types.empty?
167
+ 'pass'
168
+ else
169
+ 'fail'
170
+ end
171
+ {
172
+ status:, passed: report['passed'] == true, scenario: report['scenario'],
173
+ widget_ids: declared.sort, missing_widget_ids: missing.sort,
174
+ widget_types: declared_types.sort, missing_widget_types: missing_types.sort,
175
+ visible_failures:, console_errors: Array(report['console_errors']),
176
+ report: @browser_report
177
+ }
178
+ rescue JSON::ParserError => e
179
+ { status: 'fail', error: "invalid browser report: #{e.message}" }
180
+ end
181
+
182
+ def browser_failure_markers(report)
183
+ elements = Array(report['pages']).flat_map { Array(_1.dig('snapshot', 'elements')) } +
184
+ Array(report.dig('failure_snapshot', 'elements'))
185
+ elements.filter_map do |element|
186
+ text = element['text'].to_s
187
+ text if FAILURE_MARKERS.any? { text.include?(_1) }
188
+ end.uniq
189
+ end
190
+
191
+ def failures(compilation, package_inventory, native_build, browser)
192
+ [].tap do |result|
193
+ result << 'page compiler reported unsupported widgets' unless compilation[:status] == 'pass'
194
+ result << 'one or more pluggable widget modules are missing' unless package_inventory[:status] == 'pass'
195
+ result << 'native web bundle did not pass' unless native_build[:status] == 'pass'
196
+ result << 'passing browser evidence does not cover every widget contract' unless browser[:status] == 'pass'
197
+ end
198
+ end
199
+
200
+ def web_units(source)
201
+ source.units_of('Forms$Page').select { source.web_page?(_1) } +
202
+ source.units_of('Forms$Layout').select { source.web_layout?(_1) }
203
+ end
204
+
205
+ def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
206
+ end
207
+ # rubocop:enable Metrics
208
+ end