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.
- checksums.yaml +4 -4
- data/bin/mxrb +458 -10
- data/docs/de-DE/README.md +1 -0
- data/docs/de-DE/oql-sql.md +42 -0
- data/docs/de-DE/project-structure.md +201 -2
- data/docs/de-DE/runtime-ruby.md +104 -0
- data/docs/de-DE/runtime-schema-migrations.md +44 -0
- data/docs/de-DE/validation-matrix.md +42 -4
- data/docs/en-US/README.md +1 -0
- data/docs/en-US/oql-sql.md +41 -0
- data/docs/en-US/project-structure.md +192 -2
- data/docs/en-US/runtime-ruby.md +100 -0
- data/docs/en-US/runtime-schema-migrations.md +41 -0
- data/docs/en-US/validation-matrix.md +39 -4
- data/docs/pt-BR/README.md +1 -0
- data/docs/pt-BR/entity-dsl.md +8 -0
- data/docs/pt-BR/oql-sql.md +41 -0
- data/docs/pt-BR/project-structure.md +197 -2
- data/docs/pt-BR/runtime-ruby.md +108 -0
- data/docs/pt-BR/runtime-schema-migrations.md +40 -0
- data/docs/pt-BR/validation-matrix.md +42 -5
- data/docs/pt-BR/writing.md +28 -0
- data/lib/mxrb/cli/help.rb +426 -0
- data/lib/mxrb/cli/release.rb +137 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +1 -1
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +8 -1
- data/lib/mxrb/compiler/domain_document_compiler.rb +3 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +11 -1
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +76 -19
- data/lib/mxrb/compiler/legacy_custom_widget_compiler.rb +159 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +43 -7
- data/lib/mxrb/compiler/legacy_page_builder.rb +1248 -23
- data/lib/mxrb/compiler/packager.rb +27 -13
- data/lib/mxrb/compiler/page_bundle_compiler.rb +13 -4
- data/lib/mxrb/compiler/portable_packager.rb +16 -11
- data/lib/mxrb/compiler/web_bundle_builder.rb +5 -1
- data/lib/mxrb/compiler/web_shell_materializer.rb +42 -2
- data/lib/mxrb/compiler/widget_package_extractor.rb +3 -0
- data/lib/mxrb/domain_diagram/lifecycle.rb +272 -0
- data/lib/mxrb/domain_diagram.rb +409 -0
- data/lib/mxrb/dsl/builder.rb +62 -11
- data/lib/mxrb/environment.rb +226 -0
- data/lib/mxrb/exporter.rb +265 -45
- data/lib/mxrb/http/server.rb +107 -0
- data/lib/mxrb/initializer.rb +80 -9
- data/lib/mxrb/integrity/validator.rb +32 -7
- data/lib/mxrb/io/bson_codec.rb +42 -1
- data/lib/mxrb/io/mpr_file.rb +146 -3
- data/lib/mxrb/model/attribute.rb +3 -0
- data/lib/mxrb/model/entity.rb +44 -2
- data/lib/mxrb/model/microflow.rb +3 -1
- data/lib/mxrb/model/module.rb +71 -0
- data/lib/mxrb/model/page.rb +126 -15
- data/lib/mxrb/model/project.rb +3 -0
- data/lib/mxrb/modeler/catalog.rb +178 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +23 -12
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +15 -11
- data/lib/mxrb/official_marketplace.rb +9 -3
- data/lib/mxrb/oql/reverse_translator.rb +394 -0
- data/lib/mxrb/oql/server.rb +4 -8
- data/lib/mxrb/oql.rb +32 -4
- data/lib/mxrb/progress.rb +242 -0
- data/lib/mxrb/project_lifecycle.rb +5 -2
- data/lib/mxrb/ruby_app/exporter.rb +1749 -0
- data/lib/mxrb/ruby_app/preset.rb +506 -0
- data/lib/mxrb/ruby_app/session_manager.rb +122 -0
- data/lib/mxrb/ruby_app.rb +1361 -0
- data/lib/mxrb/runtime/access_control.rb +408 -0
- data/lib/mxrb/runtime/native.rb +1015 -93
- data/lib/mxrb/runtime/scheduler.rb +396 -0
- data/lib/mxrb/runtime/schema_migrator.rb +598 -0
- data/lib/mxrb/runtime/shared_store.rb +335 -0
- data/lib/mxrb/runtime/sqlite_store.rb +729 -0
- data/lib/mxrb/scaffold/generator.rb +11 -3
- data/lib/mxrb/team_server.rb +10 -7
- data/lib/mxrb/uml/activity_diagram.rb +118 -0
- data/lib/mxrb/uml/class_diagram.rb +118 -0
- data/lib/mxrb/uml/sequence_diagram.rb +112 -0
- data/lib/mxrb/uml/server.rb +146 -0
- data/lib/mxrb/uml/support.rb +35 -0
- data/lib/mxrb/version.rb +1 -1
- data/lib/mxrb/web_ui/assets/abnfDiagram-N423BO3Z-C4JD3cEC.js +1 -0
- data/lib/mxrb/web_ui/assets/arc-3Z53kgFp.js +1 -0
- data/lib/mxrb/web_ui/assets/architecture-TIHT7OUA-BStBTLea.js +1 -0
- data/lib/mxrb/web_ui/assets/architectureDiagram-T3A2C74G-DBwqnM4J.js +36 -0
- data/lib/mxrb/web_ui/assets/array-BifhSqXX.js +1 -0
- data/lib/mxrb/web_ui/assets/blockDiagram-VBNYF7ZC-DLtuHt-j.js +132 -0
- data/lib/mxrb/web_ui/assets/c4Diagram-5PPSVZJV-ConSk_pe.js +10 -0
- data/lib/mxrb/web_ui/assets/channel-BhPvD7Dt.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-2GRJ4B5K-D2HpWsf_.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-4I5QYGJK-CiIdNwaF.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-5RXB4S5H-Bu_AaEkE.js +231 -0
- data/lib/mxrb/web_ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- data/lib/mxrb/web_ui/assets/chunk-6Q2QTUOP-wQkNmt_X.js +88 -0
- data/lib/mxrb/web_ui/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-GF5L2VYU-G8MmiBzU.js +206 -0
- data/lib/mxrb/web_ui/assets/chunk-I66GZJ75-CulcrLPh.js +127 -0
- data/lib/mxrb/web_ui/assets/chunk-JQJVKLGR-ukjzcdlL.js +156 -0
- data/lib/mxrb/web_ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-KBJHAD2P-Wm35lYov.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-KEIR6QF5-BICK3FdT.js +161 -0
- data/lib/mxrb/web_ui/assets/chunk-NSK5VX7P-D2nYrCY4.js +2 -0
- data/lib/mxrb/web_ui/assets/chunk-QR6OTTB3-BrdIzg4Q.js +62 -0
- data/lib/mxrb/web_ui/assets/chunk-RYQCIY6F-CCfZMcW6.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-UBXNYLIW-BPEVYsFK.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-W5SLKNZC-2E6OPhA4.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-WRU74C26-CBIvOyWp.js +70 -0
- data/lib/mxrb/web_ui/assets/chunk-XXDRQBXY-pH58XAyl.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- data/lib/mxrb/web_ui/assets/classDiagram-JCYQIIEL-xiqo4D89.js +1 -0
- data/lib/mxrb/web_ui/assets/classDiagram-v2-OCEON4UE-xiqo4D89.js +1 -0
- data/lib/mxrb/web_ui/assets/cose-bilkent-JH36ORCC-t_flPpii.js +1 -0
- data/lib/mxrb/web_ui/assets/cynefin-VYW2F7L2-BIqktTEv.js +1 -0
- data/lib/mxrb/web_ui/assets/cynefinDiagram-MW4NZA55-C2d--lut.js +62 -0
- data/lib/mxrb/web_ui/assets/cytoscape.esm-B-NFISlW.js +321 -0
- data/lib/mxrb/web_ui/assets/dagre-Buvkdvvj.js +1 -0
- data/lib/mxrb/web_ui/assets/dagre-VZM6K2ZE-CSz2_tb5.js +4 -0
- data/lib/mxrb/web_ui/assets/defaultLocale-BFoDCU3G.js +1 -0
- data/lib/mxrb/web_ui/assets/diagram-7IWD3JNH-DCt7u7V_.js +30 -0
- data/lib/mxrb/web_ui/assets/diagram-B4RE2ZJO-DqZBqkMu.js +3 -0
- data/lib/mxrb/web_ui/assets/diagram-LBJQPF4R-ClIYbIsI.js +24 -0
- data/lib/mxrb/web_ui/assets/diagram-Q27KOJAE-DFxhvthJ.js +24 -0
- data/lib/mxrb/web_ui/assets/diagram-UB23O5K3-BSUTXptv.js +41 -0
- data/lib/mxrb/web_ui/assets/dist-D2qOEbeJ.js +1 -0
- data/lib/mxrb/web_ui/assets/domain-mGOZ1KCG.css +1 -0
- data/lib/mxrb/web_ui/assets/domain-qsjXhy4v.js +1 -0
- data/lib/mxrb/web_ui/assets/ebnfDiagram-BXEA7PRR-B5reICEi.js +1 -0
- data/lib/mxrb/web_ui/assets/erDiagram-JOGREHBK-Co5Mxn2p.js +85 -0
- data/lib/mxrb/web_ui/assets/eventmodeling-45OFAUF4-C1ylhSks.js +1 -0
- data/lib/mxrb/web_ui/assets/flowDiagram-UKHOOZJN-CHXaJIaS.js +1 -0
- data/lib/mxrb/web_ui/assets/ganttDiagram-PKOTCBZU-DPPPaWaz.js +292 -0
- data/lib/mxrb/web_ui/assets/gitGraph-TEB2WS4Q-qS77ywA9.js +1 -0
- data/lib/mxrb/web_ui/assets/gitGraphDiagram-DS77QQ5N-DpAv6-BT.js +106 -0
- data/lib/mxrb/web_ui/assets/graphlib-DS17s2tU.js +1 -0
- data/lib/mxrb/web_ui/assets/info-DKCQHKI2-C9VD1Y4e.js +1 -0
- data/lib/mxrb/web_ui/assets/infoDiagram-6WML65LV-Bk1kbcCy.js +2 -0
- data/lib/mxrb/web_ui/assets/init-C-OQMol4.js +1 -0
- data/lib/mxrb/web_ui/assets/ishikawaDiagram-WSZJBQD7-0gcISBFR.js +70 -0
- data/lib/mxrb/web_ui/assets/journeyDiagram-NVQOT4AX-Dt0RQnlh.js +139 -0
- data/lib/mxrb/web_ui/assets/jsx-runtime-DV0a5kSb.js +9 -0
- data/lib/mxrb/web_ui/assets/kanban-definition-27J2QSJJ-DIPQM7w9.js +89 -0
- data/lib/mxrb/web_ui/assets/katex-CXMH3UgJ.js +257 -0
- data/lib/mxrb/web_ui/assets/line-C0rYD-tL.js +1 -0
- data/lib/mxrb/web_ui/assets/linear-BPPxWorj.js +1 -0
- data/lib/mxrb/web_ui/assets/map-BaFkSB1l.js +1 -0
- data/lib/mxrb/web_ui/assets/mermaid-parser.core-B_UPzTxa.js +7 -0
- data/lib/mxrb/web_ui/assets/mindmap-definition-FAOFIHXS-CdJRSMuk.js +96 -0
- data/lib/mxrb/web_ui/assets/modeler-BNqHthwk.css +1 -0
- data/lib/mxrb/web_ui/assets/modeler-CQmqflbg.js +1 -0
- data/lib/mxrb/web_ui/assets/ordinal-BDEzSJ7C.js +1 -0
- data/lib/mxrb/web_ui/assets/packet-7NZHBO7P-CwQ4NSBs.js +1 -0
- data/lib/mxrb/web_ui/assets/path-fybaL0A-.js +1 -0
- data/lib/mxrb/web_ui/assets/pegDiagram-VL7TDLO6-D40S7ppx.js +1 -0
- data/lib/mxrb/web_ui/assets/pie-RZYD4A2V-CJroHMKh.js +1 -0
- data/lib/mxrb/web_ui/assets/pieDiagram-7S7Q4E2Y-QB54__BV.js +39 -0
- data/lib/mxrb/web_ui/assets/quadrantDiagram-CIZ2JOQS-DBhDS6ag.js +7 -0
- data/lib/mxrb/web_ui/assets/radar-I7S5WNFK-DdIUwd2d.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-3IZDKUUU-B1Lw6Xyq.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-abnf-AHOZXSZD-CQgQoeak.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-ebnf-EBAXGLYW-D7qEQ2x8.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-peg-LSFZ7HO6-C005Vxbb.js +1 -0
- data/lib/mxrb/web_ui/assets/railroadDiagram-AXF67PYL-BBP2qk7M.js +1 -0
- data/lib/mxrb/web_ui/assets/requirementDiagram-LRYGKXZP-D2cLsAoI.js +84 -0
- data/lib/mxrb/web_ui/assets/rough.esm-Dy-Kn_BL.js +1 -0
- data/lib/mxrb/web_ui/assets/sankeyDiagram-W5VNT64P-Bpi3FiP2.js +40 -0
- data/lib/mxrb/web_ui/assets/sequenceDiagram-SI44F4Z6-DCRcDlVe.js +162 -0
- data/lib/mxrb/web_ui/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- data/lib/mxrb/web_ui/assets/src-CvxfUak2.js +1 -0
- data/lib/mxrb/web_ui/assets/stateDiagram-OKZ733FA-C2GCEoxl.js +1 -0
- data/lib/mxrb/web_ui/assets/stateDiagram-v2-UEYNNEHI-D2gWgedh.js +1 -0
- data/lib/mxrb/web_ui/assets/swimlanes-SLNWSIFB-CRI9irEW.js +2 -0
- data/lib/mxrb/web_ui/assets/swimlanesDiagram-ULZ7WXOC-C5Wb_-hZ.js +8 -0
- data/lib/mxrb/web_ui/assets/theme-_mxc7BdY.css +1 -0
- data/lib/mxrb/web_ui/assets/timeline-definition-Z64GVDOM-B1-mNg-h.js +120 -0
- data/lib/mxrb/web_ui/assets/treeView-QDETBFTQ-7CTvIwKq.js +1 -0
- data/lib/mxrb/web_ui/assets/treemap-6X3UGDF4-CtL22pq5.js +1 -0
- data/lib/mxrb/web_ui/assets/uml-C3KAFGTq.js +51 -0
- data/lib/mxrb/web_ui/assets/uml-Dn-qd01X.css +1 -0
- data/lib/mxrb/web_ui/assets/vennDiagram-T6HMQDX7-BzVnUEAy.js +34 -0
- data/lib/mxrb/web_ui/assets/wardley-OPB4EBWU-JNi-QNag.js +1 -0
- data/lib/mxrb/web_ui/assets/wardleyDiagram-T6FBY63Y-BUwmAQ-B.js +78 -0
- data/lib/mxrb/web_ui/assets/xychartDiagram-ELKLHX3M-DEQS8sKp.js +7 -0
- data/lib/mxrb/web_ui/domain.html +15 -0
- data/lib/mxrb/web_ui/modeler.html +16 -0
- data/lib/mxrb/web_ui/uml.html +31 -0
- data/lib/mxrb/web_ui.rb +89 -0
- data/lib/mxrb/widget_certification.rb +208 -0
- data/lib/mxrb/writer.rb +278 -41
- data/lib/mxrb.rb +38 -8
- metadata +157 -5
|
@@ -19,16 +19,24 @@ module Mxrb
|
|
|
19
19
|
@deployment = File.expand_path(deployment || File.join(@project_root, 'deployment'))
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def pack(output:, force: false)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
def pack(output:, force: false) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
23
|
+
Progress.with("Packing #{File.basename(@mpr_path)}") do |progress|
|
|
24
|
+
output_path = File.expand_path(output)
|
|
25
|
+
validate_input!(output_path, force:)
|
|
26
|
+
version = Mxrb.open(@mpr_path, &:mendix_version)
|
|
27
|
+
adapter = Adapter.for(version)
|
|
28
|
+
metadata = adapter.validate_deployment!(@deployment)
|
|
29
|
+
adapter.validate_freshness!(@mpr_path, @deployment)
|
|
30
|
+
files, directories = inventory
|
|
31
|
+
progress.update(
|
|
32
|
+
current: 3, total: files.size + directories.size + 4,
|
|
33
|
+
detail: "#{files.size} files"
|
|
34
|
+
)
|
|
35
|
+
write_atomically(output_path, files, directories, progress)
|
|
36
|
+
package_result(output_path, version, metadata).tap do
|
|
37
|
+
progress.advance(detail: 'package checksum')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
32
40
|
end
|
|
33
41
|
|
|
34
42
|
private
|
|
@@ -69,13 +77,19 @@ module Mxrb
|
|
|
69
77
|
path.delete_prefix("#{@deployment}/").tr('\\', '/')
|
|
70
78
|
end
|
|
71
79
|
|
|
72
|
-
def write_atomically(output, files, directories)
|
|
80
|
+
def write_atomically(output, files, directories, progress) # rubocop:disable Metrics/MethodLength
|
|
73
81
|
FileUtils.mkdir_p(File.dirname(output))
|
|
74
82
|
Dir.mktmpdir('mxrb-mda-', File.dirname(output)) do |tmpdir|
|
|
75
83
|
temporary = File.join(tmpdir, 'package.mda')
|
|
76
84
|
Zip::File.open(temporary, create: true) do |archive|
|
|
77
|
-
directories.each
|
|
78
|
-
|
|
85
|
+
directories.each do |directory|
|
|
86
|
+
add_directory(archive, directory)
|
|
87
|
+
progress.advance(detail: relative(directory))
|
|
88
|
+
end
|
|
89
|
+
files.each do |file|
|
|
90
|
+
add_file(archive, file)
|
|
91
|
+
progress.advance(detail: relative(file))
|
|
92
|
+
end
|
|
79
93
|
end
|
|
80
94
|
FileUtils.mv(temporary, output, force: true)
|
|
81
95
|
end
|
|
@@ -341,7 +341,10 @@ module Mxrb
|
|
|
341
341
|
return grid.render
|
|
342
342
|
end
|
|
343
343
|
|
|
344
|
-
gallery = GalleryBundleCompiler.new(
|
|
344
|
+
gallery = GalleryBundleCompiler.new(
|
|
345
|
+
@source, @qualified_name, widget,
|
|
346
|
+
render_widgets: ->(widgets, scope, entity) { render_scoped_widgets(widgets, scope, entity) }
|
|
347
|
+
)
|
|
345
348
|
if gallery.supported?
|
|
346
349
|
@uses_gallery = true
|
|
347
350
|
nano_reference = nanoflow_reference(gallery.data_source.nanoflow_name) if gallery.data_source.nanoflow?
|
|
@@ -1818,17 +1821,21 @@ module Mxrb
|
|
|
1818
1821
|
if @generic_widgets&.any?
|
|
1819
1822
|
imports.concat([
|
|
1820
1823
|
'import { AttributeProperty } from "mendix/AttributeProperty";',
|
|
1824
|
+
'import { ListAttributeProperty } from "mendix/ListAttributeProperty";',
|
|
1821
1825
|
'import { ExpressionProperty } from "mendix/ExpressionProperty";',
|
|
1822
1826
|
'import { ListExpressionProperty } from "mendix/ListExpressionProperty";',
|
|
1823
|
-
'import { DatabaseObjectListProperty } from "mendix/DatabaseObjectListProperty";'
|
|
1827
|
+
'import { DatabaseObjectListProperty } from "mendix/DatabaseObjectListProperty";',
|
|
1828
|
+
'import { SelectionProperty } from "mendix/SelectionProperty";',
|
|
1829
|
+
'import { TemplatedWidgetProperty } from "mendix/TemplatedWidgetProperty";'
|
|
1824
1830
|
])
|
|
1825
1831
|
end
|
|
1826
1832
|
if @uses_data_grid
|
|
1827
1833
|
imports.concat([
|
|
1828
1834
|
'import { DatabaseObjectListProperty } from "mendix/DatabaseObjectListProperty";',
|
|
1829
|
-
'import {
|
|
1835
|
+
'import { ListAttributeProperty } from "mendix/ListAttributeProperty";',
|
|
1830
1836
|
'import { ExpressionProperty } from "mendix/ExpressionProperty";',
|
|
1831
1837
|
'import { ListExpressionProperty } from "mendix/ListExpressionProperty";',
|
|
1838
|
+
'import { SelectionProperty } from "mendix/SelectionProperty";',
|
|
1832
1839
|
'import { TemplatedWidgetProperty } from "mendix/TemplatedWidgetProperty";',
|
|
1833
1840
|
'import Datagrid from "../widgets/com/mendix/widget/web/datagrid/Datagrid.mjs";'
|
|
1834
1841
|
])
|
|
@@ -1988,7 +1995,9 @@ module Mxrb
|
|
|
1988
1995
|
|
|
1989
1996
|
def render_content(content)
|
|
1990
1997
|
JSON.pretty_generate(content).sub(/\A\{/, '{').sub(/\}\z/, '}')
|
|
1991
|
-
.gsub(/"(renderKey =>[^\n]+)"/)
|
|
1998
|
+
.gsub(/"(renderKey =>[^\n]+)"/) do
|
|
1999
|
+
JSON.parse(%("#{Regexp.last_match(1)}"))
|
|
2000
|
+
end
|
|
1992
2001
|
end
|
|
1993
2002
|
|
|
1994
2003
|
def layout
|
|
@@ -309,17 +309,22 @@ module Mxrb
|
|
|
309
309
|
@mendix_home = mendix_home && File.expand_path(mendix_home)
|
|
310
310
|
end
|
|
311
311
|
|
|
312
|
-
def pack(output:, force: false)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
312
|
+
def pack(output:, force: false) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
313
|
+
Progress.with("Packing portable Runtime for #{File.basename(@mpr_path)}") do |progress|
|
|
314
|
+
output_path = File.expand_path(output)
|
|
315
|
+
validate_output!(output_path, force:)
|
|
316
|
+
progress.update(detail: 'validating deployment')
|
|
317
|
+
version = Mxrb.open(@mpr_path, &:mendix_version)
|
|
318
|
+
adapter = Adapter.for(version)
|
|
319
|
+
metadata = adapter.validate_deployment!(@deployment)
|
|
320
|
+
adapter.validate_freshness!(@mpr_path, @deployment)
|
|
321
|
+
runtime = runtime_root(version)
|
|
322
|
+
validate_runtime!(runtime)
|
|
323
|
+
progress.update(detail: 'writing Runtime archive')
|
|
324
|
+
PortableArchiveWriter.new(@deployment, runtime, metadata).write(output_path)
|
|
325
|
+
progress.update(detail: 'calculating checksum')
|
|
326
|
+
result(output_path, version, metadata)
|
|
327
|
+
end
|
|
323
328
|
end
|
|
324
329
|
|
|
325
330
|
private
|
|
@@ -120,7 +120,11 @@ module Mxrb
|
|
|
120
120
|
constants: { LAYOUT_SCOPE_ID_PREFIX: 'l' },
|
|
121
121
|
registerServiceWorker: false, enableServiceWorkerCaching: true
|
|
122
122
|
}
|
|
123
|
-
File.write(
|
|
123
|
+
File.write(
|
|
124
|
+
path,
|
|
125
|
+
"import { startApp } from \"mendix\";\n\n" \
|
|
126
|
+
"startApp(#{JSON.pretty_generate(payload)});\n"
|
|
127
|
+
)
|
|
124
128
|
end
|
|
125
129
|
|
|
126
130
|
def languages
|
|
@@ -24,7 +24,8 @@ module Mxrb
|
|
|
24
24
|
def materialize
|
|
25
25
|
return 0 unless File.directory?(@web)
|
|
26
26
|
|
|
27
|
-
changed =
|
|
27
|
+
changed = materialize_widget_stylesheet ? 1 : 0
|
|
28
|
+
changed += html_files.count { render_html(_1) }
|
|
28
29
|
changed += materialize_dynamic_imports
|
|
29
30
|
write_missing(File.join(@web, 'js', 'login_i18n.js'), login_i18n)
|
|
30
31
|
write_missing(File.join(@web, 'lib', 'bootstrap', 'css', 'bootstrap.min.css'), login_styles)
|
|
@@ -51,6 +52,29 @@ module Mxrb
|
|
|
51
52
|
|
|
52
53
|
def html_files = Dir.glob(File.join(@web, '*.html')).sort
|
|
53
54
|
|
|
55
|
+
def materialize_widget_stylesheet
|
|
56
|
+
destination = File.join(@web, 'widgets', 'mxrb-widgets.css')
|
|
57
|
+
styles = Dir.glob(File.join(@web, 'widgets', '**', '*.css')).sort
|
|
58
|
+
styles.delete(destination)
|
|
59
|
+
return remove_widget_stylesheet(destination) if styles.empty?
|
|
60
|
+
|
|
61
|
+
rendered = widget_stylesheet(styles)
|
|
62
|
+
return false if File.file?(destination) && File.binread(destination) == rendered
|
|
63
|
+
|
|
64
|
+
File.binwrite(destination, rendered)
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def remove_widget_stylesheet(path)
|
|
69
|
+
existed = File.exist?(path)
|
|
70
|
+
FileUtils.rm_f(path)
|
|
71
|
+
existed
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def widget_stylesheet(styles)
|
|
75
|
+
styles.map { File.binread(_1) }.join("\n") << "\n"
|
|
76
|
+
end
|
|
77
|
+
|
|
54
78
|
def render_javascript(source)
|
|
55
79
|
rendered = source.gsub(DYNAMIC_PAGE_CACHE) do
|
|
56
80
|
%(`?#{cache_token}\${#{Regexp.last_match(1)}}`)
|
|
@@ -68,7 +92,7 @@ module Mxrb
|
|
|
68
92
|
rendered = rendered.gsub('{{themecss}}', theme_css)
|
|
69
93
|
rendered = rendered.gsub('{{manifest}}', manifest)
|
|
70
94
|
rendered = rendered.gsub(PLACEHOLDER, '')
|
|
71
|
-
rendered =
|
|
95
|
+
rendered = render_application_html(rendered) if File.basename(path) == 'index.html'
|
|
72
96
|
return false if source == rendered
|
|
73
97
|
|
|
74
98
|
File.write(path, rendered)
|
|
@@ -115,6 +139,22 @@ module Mxrb
|
|
|
115
139
|
html.sub('</head>', "#{navigation_compatibility}\n</head>")
|
|
116
140
|
end
|
|
117
141
|
|
|
142
|
+
def render_application_html(html)
|
|
143
|
+
inject_navigation_compatibility(inject_widget_stylesheet(html))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def inject_widget_stylesheet(html)
|
|
147
|
+
return html unless File.file?(File.join(@web, 'widgets', 'mxrb-widgets.css'))
|
|
148
|
+
|
|
149
|
+
stylesheet = '<link data-mxrb-widget-styles rel="stylesheet" ' \
|
|
150
|
+
"href=\"widgets/mxrb-widgets.css?#{cache_token}\">"
|
|
151
|
+
return html.sub(/<link\b[^>]*data-mxrb-widget-styles[^>]*>/, stylesheet) if
|
|
152
|
+
html.include?('data-mxrb-widget-styles')
|
|
153
|
+
return html unless html.include?('</head>')
|
|
154
|
+
|
|
155
|
+
html.sub('</head>', "#{stylesheet}\n</head>")
|
|
156
|
+
end
|
|
157
|
+
|
|
118
158
|
def navigation_compatibility # rubocop:disable Metrics/MethodLength
|
|
119
159
|
<<~HTML.chomp
|
|
120
160
|
<script data-mxrb-navigation-compat="#{CACHE_REVISION}">
|
|
@@ -10,10 +10,13 @@ module Mxrb
|
|
|
10
10
|
class WidgetPackageExtractor
|
|
11
11
|
def initialize(project_root, web_root)
|
|
12
12
|
@source = File.join(project_root, 'widgets')
|
|
13
|
+
@web_root = web_root
|
|
13
14
|
@destination = File.join(web_root, 'widgets')
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def extract
|
|
18
|
+
FileUtils.rm_f(File.join(@web_root, 'mxrb-widgets.css'))
|
|
19
|
+
FileUtils.rm_f(File.join(@destination, 'mxrb-widgets.css'))
|
|
17
20
|
return 0 unless File.directory?(@source)
|
|
18
21
|
|
|
19
22
|
FileUtils.mkdir_p(@destination)
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'net/http'
|
|
7
|
+
require 'rbconfig'
|
|
8
|
+
require 'securerandom'
|
|
9
|
+
require 'time'
|
|
10
|
+
|
|
11
|
+
module Mxrb
|
|
12
|
+
module DomainDiagram
|
|
13
|
+
# Background lifecycle for the ER editor. Control happens through a
|
|
14
|
+
# token-authenticated loopback endpoint rather than a potentially stale PID.
|
|
15
|
+
# rubocop:disable Metrics
|
|
16
|
+
class Lifecycle
|
|
17
|
+
STATE_ROOT_ENV = 'MXRB_DIAGRAM_STATE_ROOT'
|
|
18
|
+
START_TIMEOUT = 8
|
|
19
|
+
STOP_TIMEOUT = 8
|
|
20
|
+
|
|
21
|
+
Status = Data.define(:state, :source, :output, :url, :pid, :log) do
|
|
22
|
+
def running? = state == 'running'
|
|
23
|
+
def to_h = { state:, source:, output:, url:, pid:, log: }.compact
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :source, :state_root, :executable
|
|
27
|
+
|
|
28
|
+
def initialize(source, state_root: self.class.default_state_root, executable: $PROGRAM_NAME)
|
|
29
|
+
@source = File.expand_path(source)
|
|
30
|
+
@state_root = File.expand_path(state_root)
|
|
31
|
+
@executable = File.expand_path(executable)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.default_state_root
|
|
35
|
+
configured = ENV[STATE_ROOT_ENV]
|
|
36
|
+
return configured unless configured.to_s.empty?
|
|
37
|
+
|
|
38
|
+
File.join(Dir.home, '.local', 'state', 'mxrb', 'diagram-er')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def up(output: nil, modules: nil, port: nil, force: false)
|
|
42
|
+
previous = read_state
|
|
43
|
+
raise ArgumentError, "diagram already running at #{url_for(previous)}" if running?(previous)
|
|
44
|
+
raise ArgumentError, 'managed diagram is already starting' if starting?(previous)
|
|
45
|
+
|
|
46
|
+
settings = settings_for(previous, output:, modules:, port:)
|
|
47
|
+
managed_paths = prepare_output(previous, settings, force:)
|
|
48
|
+
token = SecureRandom.hex(24)
|
|
49
|
+
state = settings.merge(
|
|
50
|
+
'version' => 1, 'source' => source, 'token' => token, 'state' => 'starting',
|
|
51
|
+
'pid' => nil, 'log' => log_path, 'managed_paths' => managed_paths,
|
|
52
|
+
'created_at' => Time.now.utc.iso8601
|
|
53
|
+
)
|
|
54
|
+
write_state(state)
|
|
55
|
+
pid = spawn_worker(state)
|
|
56
|
+
Process.detach(pid)
|
|
57
|
+
write_state(read_state.merge('pid' => pid))
|
|
58
|
+
wait_until(START_TIMEOUT) { running?(read_state, token:) }
|
|
59
|
+
status
|
|
60
|
+
rescue StandardError
|
|
61
|
+
mark_stopped(token) if defined?(token) && token
|
|
62
|
+
raise
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def status
|
|
66
|
+
state = read_state
|
|
67
|
+
return Status.new('absent', source, nil, nil, nil, nil) unless state
|
|
68
|
+
|
|
69
|
+
lifecycle_state = if running?(state)
|
|
70
|
+
'running'
|
|
71
|
+
elsif starting?(state)
|
|
72
|
+
'starting'
|
|
73
|
+
else
|
|
74
|
+
'stopped'
|
|
75
|
+
end
|
|
76
|
+
Status.new(
|
|
77
|
+
lifecycle_state, source, state['output'], url_for(state), state['pid'], state['log']
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def down
|
|
82
|
+
state = read_state or raise ArgumentError, "no managed diagram for #{source}"
|
|
83
|
+
unless running?(state)
|
|
84
|
+
raise ArgumentError, 'managed diagram is still starting; retry in a moment' if starting?(state)
|
|
85
|
+
|
|
86
|
+
mark_stopped(state.fetch('token'))
|
|
87
|
+
return status
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
response = request(state, Net::HTTP::Post, '/api/admin/shutdown')
|
|
91
|
+
raise ArgumentError, 'managed diagram refused the shutdown request' unless response.is_a?(Net::HTTPSuccess)
|
|
92
|
+
|
|
93
|
+
wait_until(STOP_TIMEOUT) { !running?(read_state) }
|
|
94
|
+
mark_stopped(state.fetch('token'))
|
|
95
|
+
status
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def destroy(confirm: false)
|
|
99
|
+
raise ArgumentError, 'destroy requires --yes' unless confirm
|
|
100
|
+
|
|
101
|
+
state = read_state or raise ArgumentError, "no managed diagram for #{source}"
|
|
102
|
+
current = status
|
|
103
|
+
down if %w[running starting].include?(current.state)
|
|
104
|
+
Array(state['managed_paths']).reverse_each do |path|
|
|
105
|
+
remove_managed_path(path, state.fetch('output'))
|
|
106
|
+
end
|
|
107
|
+
FileUtils.remove_entry_secure(instance_dir) if File.exist?(instance_dir)
|
|
108
|
+
Status.new('absent', source, nil, nil, nil, nil)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Internal worker entrypoint used by `mxrb diagram-er __serve`.
|
|
112
|
+
def run_worker(output:, modules:, port:, token:)
|
|
113
|
+
state = read_state or raise ArgumentError, 'diagram lifecycle state is missing'
|
|
114
|
+
raise ArgumentError, 'diagram lifecycle token does not match' unless state['token'] == token
|
|
115
|
+
|
|
116
|
+
server = Server.new(
|
|
117
|
+
source, output:, modules:, port:, reuse: true, lifecycle_token: token
|
|
118
|
+
)
|
|
119
|
+
trap('INT') { Thread.new { server.shutdown } }
|
|
120
|
+
trap('TERM') { Thread.new { server.shutdown } }
|
|
121
|
+
server.start do
|
|
122
|
+
write_state(state.merge(
|
|
123
|
+
'pid' => Process.pid, 'state' => 'running',
|
|
124
|
+
'started_at' => Time.now.utc.iso8601
|
|
125
|
+
))
|
|
126
|
+
end
|
|
127
|
+
ensure
|
|
128
|
+
mark_stopped(token) if defined?(token) && token
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def settings_for(previous, output:, modules:, port:)
|
|
134
|
+
selected_output = File.expand_path(output || previous&.fetch('output', nil) || default_output)
|
|
135
|
+
if previous && output && selected_output != File.expand_path(previous.fetch('output'))
|
|
136
|
+
raise ArgumentError, 'destroy the existing managed diagram before changing --output'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
'output' => selected_output,
|
|
141
|
+
'modules' => modules.nil? ? Array(previous&.fetch('modules', [])) : Array(modules),
|
|
142
|
+
'port' => Integer(port || previous&.fetch('port', nil) || 4568),
|
|
143
|
+
'host' => '127.0.0.1'
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def prepare_output(previous, settings, force:)
|
|
148
|
+
output = settings.fetch('output')
|
|
149
|
+
if previous && File.file?(output) && !force
|
|
150
|
+
Server.new(source, output:, port: settings.fetch('port'), reuse: true)
|
|
151
|
+
return Array(previous['managed_paths'])
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
server = Server.new(source, output:, port: settings.fetch('port'), force:)
|
|
155
|
+
(Array(previous&.fetch('managed_paths', [])) + server.managed_paths).uniq
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def spawn_worker(state)
|
|
159
|
+
arguments = [
|
|
160
|
+
RbConfig.ruby, executable, 'diagram-er', '__serve', source,
|
|
161
|
+
'--output', state.fetch('output'), '--port', state.fetch('port').to_s,
|
|
162
|
+
'--token', state.fetch('token'), '--state-root', state_root
|
|
163
|
+
]
|
|
164
|
+
Array(state['modules']).each { arguments.concat(['--module', _1.to_s]) }
|
|
165
|
+
prepare_instance_dir
|
|
166
|
+
FileUtils.touch(state.fetch('log'))
|
|
167
|
+
File.chmod(0o600, state.fetch('log'))
|
|
168
|
+
Process.spawn(*arguments, out: state.fetch('log'), err: %i[child out])
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def starting?(state)
|
|
172
|
+
return false unless state&.fetch('state', nil) == 'starting'
|
|
173
|
+
|
|
174
|
+
created_at = Time.iso8601(state.fetch('created_at'))
|
|
175
|
+
Time.now.utc - created_at <= START_TIMEOUT
|
|
176
|
+
rescue ArgumentError, KeyError
|
|
177
|
+
false
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def running?(state, token: state&.fetch('token', nil))
|
|
181
|
+
return false unless state && token
|
|
182
|
+
|
|
183
|
+
response = request(state, Net::HTTP::Get, '/api/admin/status', token:)
|
|
184
|
+
response&.is_a?(Net::HTTPSuccess) == true
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def request(state, request_class, path, token: state.fetch('token'))
|
|
188
|
+
request = request_class.new(path)
|
|
189
|
+
request['X-MXRB-Lifecycle-Token'] = token
|
|
190
|
+
Net::HTTP.start(
|
|
191
|
+
state.fetch('host'), Integer(state.fetch('port')),
|
|
192
|
+
open_timeout: 0.25, read_timeout: 0.5
|
|
193
|
+
) { _1.request(request) }
|
|
194
|
+
rescue IOError, SystemCallError, Timeout::Error, SocketError
|
|
195
|
+
nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def wait_until(timeout)
|
|
199
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
200
|
+
loop do
|
|
201
|
+
return true if yield
|
|
202
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
203
|
+
raise ArgumentError, "diagram lifecycle timed out; see #{log_path}"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
sleep 0.05
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def mark_stopped(token)
|
|
211
|
+
state = read_state
|
|
212
|
+
return unless state && state['token'] == token
|
|
213
|
+
|
|
214
|
+
write_state(state.merge(
|
|
215
|
+
'pid' => nil, 'state' => 'stopped',
|
|
216
|
+
'stopped_at' => Time.now.utc.iso8601
|
|
217
|
+
))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def remove_managed_path(path, output)
|
|
221
|
+
expanded = File.expand_path(path)
|
|
222
|
+
output = File.expand_path(output)
|
|
223
|
+
external = File.join(File.dirname(output), 'mprcontents')
|
|
224
|
+
raise ArgumentError, 'refusing to remove the source MPR' if expanded == source
|
|
225
|
+
|
|
226
|
+
unless expanded == output || expanded == external
|
|
227
|
+
raise ArgumentError, "refusing to remove unmanaged path #{expanded}"
|
|
228
|
+
end
|
|
229
|
+
return unless File.exist?(expanded) || File.symlink?(expanded)
|
|
230
|
+
|
|
231
|
+
if File.directory?(expanded) && !File.symlink?(expanded)
|
|
232
|
+
FileUtils.remove_entry_secure(expanded)
|
|
233
|
+
else
|
|
234
|
+
FileUtils.rm_f(expanded)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def read_state
|
|
239
|
+
JSON.parse(File.binread(state_path))
|
|
240
|
+
rescue Errno::ENOENT
|
|
241
|
+
nil
|
|
242
|
+
rescue JSON::ParserError => e
|
|
243
|
+
raise ArgumentError, "invalid diagram lifecycle state: #{e.message}"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def write_state(payload)
|
|
247
|
+
prepare_instance_dir
|
|
248
|
+
temporary = "#{state_path}.#{Process.pid}.tmp"
|
|
249
|
+
File.binwrite(temporary, JSON.pretty_generate(payload))
|
|
250
|
+
File.chmod(0o600, temporary)
|
|
251
|
+
File.rename(temporary, state_path)
|
|
252
|
+
ensure
|
|
253
|
+
FileUtils.rm_f(temporary) if defined?(temporary) && temporary
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def default_output
|
|
257
|
+
File.join(File.dirname(source), "#{File.basename(source, '.mpr')}.domain-layout.mpr")
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def prepare_instance_dir
|
|
261
|
+
FileUtils.mkdir_p(instance_dir)
|
|
262
|
+
File.chmod(0o700, instance_dir)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def url_for(state) = "http://#{state.fetch('host')}:#{state.fetch('port')}"
|
|
266
|
+
def instance_dir = File.join(state_root, Digest::SHA256.hexdigest(source)[0, 24])
|
|
267
|
+
def state_path = File.join(instance_dir, 'state.json')
|
|
268
|
+
def log_path = File.join(instance_dir, 'server.log')
|
|
269
|
+
end
|
|
270
|
+
# rubocop:enable Metrics
|
|
271
|
+
end
|
|
272
|
+
end
|