mxrb 0.1.2 → 0.1.3
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 +3 -0
- data/docs/pt-BR/entity-dsl.md +8 -0
- data/docs/pt-BR/writing.md +28 -0
- data/lib/mxrb/compiler/packager.rb +27 -13
- data/lib/mxrb/compiler/portable_packager.rb +16 -11
- data/lib/mxrb/dsl/builder.rb +19 -2
- data/lib/mxrb/exporter.rb +178 -32
- data/lib/mxrb/integrity/validator.rb +18 -7
- data/lib/mxrb/model/entity.rb +17 -1
- data/lib/mxrb/model/module.rb +71 -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.rb +9 -3
- data/lib/mxrb/progress.rb +242 -0
- data/lib/mxrb/team_server.rb +10 -7
- data/lib/mxrb/version.rb +1 -1
- data/lib/mxrb/writer.rb +79 -18
- data/lib/mxrb.rb +12 -7
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a734dae7c32bec492facdf80e775897c3b31af47fc595f4d4096f2cd27f091ee
|
|
4
|
+
data.tar.gz: db3a5207301da5c8f384515adf334c5739fba69379aec3c69175a653e68beff2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4aedde89b67e5d52e438b38e2f2c9835a9ca2407950a6c7ee04f10aa74fb0d1e9fae0fc380b50e3c3155fc7509eb0d513e90461c8058949849f9c4146df162c7
|
|
7
|
+
data.tar.gz: ed923449e412dc172462c951373408558244983db0d84b38d2a550791219190146064d900e2b6eb459fc8e6aa250def2c62614854e1e8b25d13192a6b437bd6e
|
data/bin/mxrb
CHANGED
|
@@ -64,7 +64,9 @@ end
|
|
|
64
64
|
|
|
65
65
|
SCAFFOLD_COMMANDS = (Mxrb::Scaffold::Help::COMMANDS.keys - ["design"]).freeze
|
|
66
66
|
|
|
67
|
+
progress_disabled = ARGV.delete("--no-progress")
|
|
67
68
|
command = ARGV.shift
|
|
69
|
+
Mxrb::Progress.configure(enabled: false) if progress_disabled || ARGV.include?("--json")
|
|
68
70
|
|
|
69
71
|
case command
|
|
70
72
|
when '--version', '-v'
|
|
@@ -1506,6 +1508,7 @@ else
|
|
|
1506
1508
|
mxrb — Pure-Ruby Mendix .mpr toolkit
|
|
1507
1509
|
|
|
1508
1510
|
Commands:
|
|
1511
|
+
--no-progress Disable interactive progress rendering
|
|
1509
1512
|
--version, -v Show the installed MXRB version
|
|
1510
1513
|
doctor [DIR] [--json] Check project files and local toolchain
|
|
1511
1514
|
benchmark <file.mpr> Measure open, index and validation time
|
data/docs/pt-BR/entity-dsl.md
CHANGED
|
@@ -94,6 +94,12 @@ entity :AnimalSearchResult do
|
|
|
94
94
|
string :Name
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
entity :AnimalSummaryView do
|
|
98
|
+
oql_view source: "VetClinic.AnimalSummarySource"
|
|
99
|
+
string :Name
|
|
100
|
+
integer :Total
|
|
101
|
+
end
|
|
102
|
+
|
|
97
103
|
entity :Animal do
|
|
98
104
|
before_commit microflow: "VetClinic.VAL_Animal"
|
|
99
105
|
after_commit microflow: "VetClinic.ACT_AfterAnimalCommit"
|
|
@@ -111,6 +117,8 @@ end
|
|
|
111
117
|
|
|
112
118
|
Em `access_rule`, `read:` e `write:` aceitam `:all`, `:none` ou uma lista de
|
|
113
119
|
atributos. Declare um ou mais papéis qualificados como primeiros argumentos.
|
|
120
|
+
`oql_view` liga a entidade ao `ViewEntitySourceDocument`; ao exportar um projeto
|
|
121
|
+
existente, a consulta OQL aparece junto da entidade em `domain/oql_views`.
|
|
114
122
|
|
|
115
123
|
Use `mxrb entity --help` para o comando e `mxrb generate project.rb` seguido de
|
|
116
124
|
`mxrb validate App.mpr` para validar o resultado.
|
data/docs/pt-BR/writing.md
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
`mxrb generate` avalia uma definição Ruby e cria ou atualiza o MPR. Nomes
|
|
6
6
|
Mendix são chaves estáveis, portanto reaplicar a definição não duplica units.
|
|
7
7
|
|
|
8
|
+
Operações demoradas exibem progresso em `stderr` quando executadas em um
|
|
9
|
+
terminal interativo. Quando o total é conhecido, o MXRB mostra barra e
|
|
10
|
+
porcentagem; operações externas sem total confiável mostram spinner e tempo
|
|
11
|
+
decorrido. Pipes, redirecionamentos e saídas `--json` permanecem limpos. Use
|
|
12
|
+
`--no-progress` ou `MXRB_PROGRESS=0` para desativar explicitamente.
|
|
13
|
+
|
|
8
14
|
Para iniciar em uma pasta vazia:
|
|
9
15
|
|
|
10
16
|
```sh
|
|
@@ -122,6 +128,28 @@ usam `bson_binary`. Alterações nesse Ruby sobrepõem o baseline antes das escr
|
|
|
122
128
|
tipadas. Corpos de flow possuem `body_fingerprint`: o grafo nativo é reutilizado
|
|
123
129
|
quando o Ruby não mudou e regenerado após uma edição.
|
|
124
130
|
|
|
131
|
+
Export mappings, import mappings e JSON structures são materializados por
|
|
132
|
+
módulo em `infrastructure/mappings/{exports,imports,json_structures}`. Esses
|
|
133
|
+
arquivos mantêm `UnitID` e container/pasta originais, podem editar toda a
|
|
134
|
+
estrutura profunda em Ruby e têm precedência sobre o baseline durante o
|
|
135
|
+
`generate`.
|
|
136
|
+
|
|
137
|
+
Serviços REST, OData e web services publicados são exportados para
|
|
138
|
+
`infrastructure/endpoints`; recursos, operações, métodos HTTP, paths,
|
|
139
|
+
parâmetros e referências a microflows permanecem no mesmo arquivo Ruby.
|
|
140
|
+
Serviços consumidos são roteados para `infrastructure/integrations`, enquanto
|
|
141
|
+
message definitions e XML schemas ficam em `infrastructure/mappings`.
|
|
142
|
+
|
|
143
|
+
O Domain Model exportado separa entidades persistentes em `domain/entities`,
|
|
144
|
+
DTOs/non-persistent em `domain/dtos` e entidades de OQL View em
|
|
145
|
+
`domain/oql_views`. O arquivo da OQL View também contém seu
|
|
146
|
+
`DomainModels$ViewEntitySourceDocument`, portanto a consulta `Oql` pode ser
|
|
147
|
+
editada no mesmo Ruby sem perder a referência da entidade. Datasets OQL ficam
|
|
148
|
+
em `application/queries/datasets`; enumerations e constants ficam em
|
|
149
|
+
`domain/enumerations` e `domain/constants`. Scheduled events ficam em
|
|
150
|
+
`application/jobs/scheduled_events`. O `domain/model.rb` e o
|
|
151
|
+
`application/application.rb` carregam automaticamente todas essas categorias.
|
|
152
|
+
|
|
125
153
|
A DSL cobre criação/alteração/retrieve/commit/delete, chamadas de microflow,
|
|
126
154
|
Java, JavaScript, nanoflow e app service, páginas, REST, listas, decisões,
|
|
127
155
|
loops, eventos de erro e rescue.
|
|
@@ -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
|
|
@@ -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
|
data/lib/mxrb/dsl/builder.rb
CHANGED
|
@@ -872,15 +872,21 @@ module Mxrb
|
|
|
872
872
|
@scheduled_events << sb.to_h
|
|
873
873
|
end
|
|
874
874
|
|
|
875
|
-
def native_document(name, type:, deep_structure:, containment: 'Documents'
|
|
875
|
+
def native_document(name, type:, deep_structure:, containment: 'Documents',
|
|
876
|
+
unit_id: nil, container_id: nil)
|
|
876
877
|
raise ArgumentError, 'deep_structure requires a Hash' unless deep_structure.is_a?(Hash)
|
|
877
878
|
|
|
878
879
|
@native_documents << {
|
|
879
880
|
name: name.to_s, type: type.to_s, containment: containment.to_s,
|
|
881
|
+
unit_id: unit_id&.to_s, container_id: container_id&.to_s,
|
|
880
882
|
doc: { '$Type' => type.to_s, 'Name' => name.to_s }.merge(deep_structure)
|
|
881
883
|
}
|
|
882
884
|
end
|
|
883
885
|
|
|
886
|
+
def bson_binary(base64, subtype: :generic)
|
|
887
|
+
BSON::Binary.new(Base64.strict_decode64(base64), subtype.to_sym)
|
|
888
|
+
end
|
|
889
|
+
|
|
884
890
|
# Native responsive application shell. It deliberately uses only Mendix
|
|
885
891
|
# core widgets, so generated projects do not depend on Atlas Core merely
|
|
886
892
|
# to display their navigation.
|
|
@@ -1070,6 +1076,7 @@ module Mxrb
|
|
|
1070
1076
|
@generalization = nil
|
|
1071
1077
|
@system_members = nil
|
|
1072
1078
|
@indexes = nil
|
|
1079
|
+
@oql_view = nil
|
|
1073
1080
|
end
|
|
1074
1081
|
|
|
1075
1082
|
ATTR_TYPES.each do |type|
|
|
@@ -1081,6 +1088,16 @@ module Mxrb
|
|
|
1081
1088
|
def non_persistent! = (@persistable = false)
|
|
1082
1089
|
def documentation(d) = (@doc = d)
|
|
1083
1090
|
|
|
1091
|
+
# Marks an entity as an OQL-backed view. Modern Mendix projects keep the
|
|
1092
|
+
# query in a separate ViewEntitySourceDocument; older projects may keep
|
|
1093
|
+
# it directly on the embedded entity.
|
|
1094
|
+
def oql_view(source: nil, query: nil)
|
|
1095
|
+
raise ArgumentError, 'oql_view requires source or query' if source.nil? && query.nil?
|
|
1096
|
+
|
|
1097
|
+
@oql_view = { source: source&.to_s, query: query&.to_s }.compact
|
|
1098
|
+
@persistable = false
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1084
1101
|
def generalizes(entity)
|
|
1085
1102
|
@generalization = entity.to_s
|
|
1086
1103
|
end
|
|
@@ -1164,7 +1181,7 @@ module Mxrb
|
|
|
1164
1181
|
name: @name, persistable: @persistable, documentation: @doc,
|
|
1165
1182
|
attributes: @attributes, associations: @associations, lifecycle: @lifecycle,
|
|
1166
1183
|
access_rules: @access_rules, generalization: @generalization,
|
|
1167
|
-
system_members: @system_members, indexes: @indexes
|
|
1184
|
+
system_members: @system_members, indexes: @indexes, oql_view: @oql_view
|
|
1168
1185
|
}
|
|
1169
1186
|
end
|
|
1170
1187
|
|
data/lib/mxrb/exporter.rb
CHANGED
|
@@ -9,6 +9,7 @@ module Mxrb
|
|
|
9
9
|
# Exports an MPR into an editable, layered Ruby source tree.
|
|
10
10
|
class Exporter
|
|
11
11
|
LAYERS = %w[domain application presentation infrastructure].freeze
|
|
12
|
+
MODULE_PROGRESS_WEIGHT = 10
|
|
12
13
|
MARKETPLACE_PROVENANCE = [
|
|
13
14
|
".mxrb/marketplace.lock.json",
|
|
14
15
|
".mxrb/marketplace",
|
|
@@ -63,22 +64,30 @@ module Mxrb
|
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
def export!(parallel: true)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
67
|
+
Progress.with("Exporting #{File.basename(@mpr_path)}") do |progress|
|
|
68
|
+
FileUtils.mkdir_p(@output_dir)
|
|
69
|
+
Mxrb.open(@mpr_path) do |project|
|
|
70
|
+
@architecture = project.architecture_definition
|
|
71
|
+
units = project.all_units
|
|
72
|
+
modules = project.modules
|
|
73
|
+
assets = project_asset_files
|
|
74
|
+
progress.update(
|
|
75
|
+
current: 0,
|
|
76
|
+
total: 4 + assets.size + (units.size * 2) + (modules.size * MODULE_PROGRESS_WEIGHT),
|
|
77
|
+
detail: "preparing Ruby project"
|
|
78
|
+
)
|
|
79
|
+
export_app_structure
|
|
80
|
+
progress.advance(detail: "project structure")
|
|
81
|
+
export_project_assets(assets, progress)
|
|
82
|
+
export_native_units(project, units, progress)
|
|
83
|
+
export_security(project)
|
|
84
|
+
progress.advance(detail: "project security")
|
|
85
|
+
export_architecture_contracts(project)
|
|
86
|
+
progress.advance(detail: "navigation and design system")
|
|
87
|
+
export_modules(modules, progress, parallel:)
|
|
88
|
+
write_project(project)
|
|
89
|
+
progress.advance(detail: "project.rb")
|
|
80
90
|
end
|
|
81
|
-
write_project(project)
|
|
82
91
|
end
|
|
83
92
|
@output_dir
|
|
84
93
|
end
|
|
@@ -91,6 +100,8 @@ module Mxrb
|
|
|
91
100
|
|
|
92
101
|
export_domain(root, mod)
|
|
93
102
|
export_microflows(root, mod)
|
|
103
|
+
export_application_documents(root, mod)
|
|
104
|
+
export_infrastructure_documents(root, mod)
|
|
94
105
|
export_pages(root, mod)
|
|
95
106
|
export_menus(root, mod)
|
|
96
107
|
export_nanoflows(root, mod)
|
|
@@ -106,25 +117,31 @@ module Mxrb
|
|
|
106
117
|
].each { write(File.join(@output_dir, _1, ".keep"), "") }
|
|
107
118
|
end
|
|
108
119
|
|
|
109
|
-
def
|
|
120
|
+
def project_asset_files
|
|
110
121
|
source_root = File.dirname(@mpr_path)
|
|
111
|
-
|
|
122
|
+
(Model::DesignSystem::ASSET_DIRECTORIES + MARKETPLACE_PROVENANCE).flat_map do |directory|
|
|
112
123
|
root = File.join(source_root, directory)
|
|
113
124
|
next [root] if File.file?(root) && !File.symlink?(root)
|
|
114
125
|
next [] unless File.directory?(root) && !File.symlink?(root)
|
|
115
126
|
|
|
116
127
|
Dir.glob(File.join(root, "**", "*"), File::FNM_DOTMATCH)
|
|
117
128
|
end.select { File.file?(_1) && !File.symlink?(_1) }.sort
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def export_project_assets(files, progress)
|
|
132
|
+
source_root = File.dirname(@mpr_path)
|
|
118
133
|
entries = files.map do |source|
|
|
119
134
|
relative = Pathname.new(source).relative_path_from(Pathname.new(source_root)).to_s
|
|
120
135
|
target = File.join(@output_dir, relative)
|
|
121
136
|
FileUtils.mkdir_p(File.dirname(target))
|
|
122
137
|
FileUtils.cp(source, target)
|
|
123
|
-
{
|
|
138
|
+
result = {
|
|
124
139
|
"path" => relative,
|
|
125
140
|
"size" => File.size(source),
|
|
126
141
|
"sha256" => Digest::SHA256.file(source).hexdigest
|
|
127
142
|
}
|
|
143
|
+
progress.advance(detail: "asset #{relative}")
|
|
144
|
+
result
|
|
128
145
|
end
|
|
129
146
|
write(
|
|
130
147
|
File.join(@output_dir, ".mxrb", "assets.json"),
|
|
@@ -132,8 +149,8 @@ module Mxrb
|
|
|
132
149
|
)
|
|
133
150
|
end
|
|
134
151
|
|
|
135
|
-
def export_native_units(project)
|
|
136
|
-
units =
|
|
152
|
+
def export_native_units(project, project_units, progress)
|
|
153
|
+
units = project_units.filter_map do |unit|
|
|
137
154
|
doc = project.parse_bson(unit)
|
|
138
155
|
type = doc["$Type"]
|
|
139
156
|
next if type.to_s.empty? || type == "Projects$Project"
|
|
@@ -147,16 +164,21 @@ module Mxrb
|
|
|
147
164
|
"type" => type,
|
|
148
165
|
"contents" => Base64.strict_encode64(IO::BsonCodec.serialize(doc))
|
|
149
166
|
}
|
|
167
|
+
ensure
|
|
168
|
+
progress.advance(detail: "native baseline")
|
|
150
169
|
end
|
|
151
170
|
write(
|
|
152
171
|
File.join(@output_dir, ".mxrb", "native_units.json"),
|
|
153
172
|
JSON.pretty_generate("format_version" => project.format_version.to_s, "units" => units)
|
|
154
173
|
)
|
|
155
|
-
source =
|
|
174
|
+
source = project_units.filter_map do |unit|
|
|
156
175
|
doc = project.parse_bson(unit)
|
|
157
176
|
next if doc["$Type"].to_s.empty? || doc["$Type"] == "Projects$Project"
|
|
177
|
+
next if Model::Module::EDITABLE_DOCUMENT_TYPES.include?(doc["$Type"])
|
|
158
178
|
|
|
159
179
|
native_unit_source(project, unit, doc)
|
|
180
|
+
ensure
|
|
181
|
+
progress.advance(detail: "editable native units")
|
|
160
182
|
end
|
|
161
183
|
write(
|
|
162
184
|
File.join(@output_dir, ".mxrb", "native_units.rb"),
|
|
@@ -164,6 +186,18 @@ module Mxrb
|
|
|
164
186
|
)
|
|
165
187
|
end
|
|
166
188
|
|
|
189
|
+
def export_modules(modules, progress, parallel:)
|
|
190
|
+
operation = lambda do |mod|
|
|
191
|
+
export_module(mod)
|
|
192
|
+
progress.advance(MODULE_PROGRESS_WEIGHT, detail: "module #{mod.name}")
|
|
193
|
+
end
|
|
194
|
+
if parallel && modules.size > 1
|
|
195
|
+
modules.map { |mod| Thread.new { operation.call(mod) } }.each(&:join)
|
|
196
|
+
else
|
|
197
|
+
modules.each { operation.call(_1) }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
167
201
|
def native_unit_source(project, unit, doc)
|
|
168
202
|
<<~RUBY
|
|
169
203
|
native_unit #{ruby(unit.fetch("UnitID"))},
|
|
@@ -211,8 +245,10 @@ module Mxrb
|
|
|
211
245
|
|
|
212
246
|
def export_module_scaffolding(root)
|
|
213
247
|
%w[
|
|
214
|
-
domain/
|
|
215
|
-
|
|
248
|
+
domain/entities domain/dtos domain/oql_views domain/enumerations domain/constants
|
|
249
|
+
domain/rules domain/policies
|
|
250
|
+
application/ports/repositories application/queries application/queries/datasets
|
|
251
|
+
application/validations application/jobs application/jobs/scheduled_events
|
|
216
252
|
presentation/features presentation/client_actions presentation/snippets presentation/view_models
|
|
217
253
|
presentation/menus
|
|
218
254
|
infrastructure/persistence/mendix infrastructure/persistence/external
|
|
@@ -238,26 +274,62 @@ module Mxrb
|
|
|
238
274
|
|
|
239
275
|
def export_domain(root, mod)
|
|
240
276
|
domain = File.join(root, "domain")
|
|
241
|
-
entities_dir = File.join(domain, "entities")
|
|
242
|
-
FileUtils.mkdir_p(entities_dir)
|
|
243
|
-
|
|
244
277
|
associations = mod.associations.group_by(&:from_entity_id)
|
|
245
278
|
architecture = architecture_module(mod.name)
|
|
246
|
-
|
|
279
|
+
paths = {}
|
|
280
|
+
grouped = mod.entities.group_by { entity_domain_route(_1) }
|
|
281
|
+
grouped.each do |route, entities|
|
|
282
|
+
unique_entity_filenames(entities).each do |id, filename|
|
|
283
|
+
paths[id] = File.join(route, filename)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
domain_documents = mod.domain_documents
|
|
287
|
+
oql_documents = mod.oql_view_documents
|
|
288
|
+
attached_oql_documents = {}
|
|
247
289
|
mod.entities.each do |entity|
|
|
248
290
|
entity_metadata = architecture&.fetch(:entities, [])&.find { _1[:name] == entity.name }
|
|
291
|
+
source = entity_source(entity, mod, associations.fetch(entity.id, []), entity_metadata)
|
|
292
|
+
if oql_view_entity?(entity) && (document = oql_document_for(entity, oql_documents, mod.name))
|
|
293
|
+
source = "#{source.rstrip}\n\n#{native_document_declaration(document)}"
|
|
294
|
+
attached_oql_documents[document.fetch(:id)] = true
|
|
295
|
+
end
|
|
249
296
|
write(
|
|
250
|
-
File.join(
|
|
251
|
-
entity_source(entity, mod, associations.fetch(entity.id, []), entity_metadata)
|
|
297
|
+
File.join(domain, paths.fetch(entity.id)), source
|
|
252
298
|
)
|
|
253
299
|
end
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
300
|
+
used = paths.values.to_h { [_1, true] }
|
|
301
|
+
domain_documents.reject { attached_oql_documents[_1.fetch(:id)] }.each do |document|
|
|
302
|
+
relative = unique_relative_path(document.fetch(:route), underscore(document.fetch(:name)), used)
|
|
303
|
+
write(File.join(domain, relative), mapping_document_source(document))
|
|
304
|
+
paths[document.fetch(:id)] = relative
|
|
305
|
+
end
|
|
306
|
+
loads = paths.values.sort.map do |relative|
|
|
307
|
+
segments = relative.split(File::SEPARATOR).map { ruby(_1) }.join(", ")
|
|
308
|
+
%(evaluate File.join(__dir__, #{segments}))
|
|
257
309
|
end
|
|
258
310
|
write(File.join(domain, "model.rb"), "#{loads.join("\n")}\n")
|
|
259
311
|
end
|
|
260
312
|
|
|
313
|
+
def entity_domain_route(entity)
|
|
314
|
+
return 'oql_views' if oql_view_entity?(entity)
|
|
315
|
+
return 'dtos' unless entity.persistable
|
|
316
|
+
|
|
317
|
+
'entities'
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def oql_view_entity?(entity)
|
|
321
|
+
entity.respond_to?(:oql_view?) && entity.oql_view?
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def oql_document_for(entity, documents, module_name)
|
|
325
|
+
reference = entity.oql_source_document.to_s
|
|
326
|
+
return nil if reference.empty?
|
|
327
|
+
|
|
328
|
+
documents.find do |document|
|
|
329
|
+
[document.fetch(:name), "#{module_name}.#{document.fetch(:name)}"].include?(reference)
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
261
333
|
def export_microflows(root, mod)
|
|
262
334
|
files = unique_filenames(mod.microflows)
|
|
263
335
|
by_layer = { "application" => [], "infrastructure" => [] }
|
|
@@ -296,6 +368,74 @@ module Mxrb
|
|
|
296
368
|
write_path_aggregator(File.join(presentation, "presentation.rb"), paths)
|
|
297
369
|
end
|
|
298
370
|
|
|
371
|
+
def export_infrastructure_documents(root, mod)
|
|
372
|
+
documents = mod.infrastructure_documents
|
|
373
|
+
return if documents.empty?
|
|
374
|
+
|
|
375
|
+
infrastructure = File.join(root, "infrastructure")
|
|
376
|
+
used = {}
|
|
377
|
+
paths = documents.map do |document|
|
|
378
|
+
base = underscore(document.fetch(:name))
|
|
379
|
+
relative = unique_relative_path(document.fetch(:route), base, used)
|
|
380
|
+
write(File.join(infrastructure, relative), mapping_document_source(document))
|
|
381
|
+
relative
|
|
382
|
+
end
|
|
383
|
+
append_to_aggregator(File.join(infrastructure, "infrastructure.rb"), paths)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def export_application_documents(root, mod)
|
|
387
|
+
documents = mod.application_documents
|
|
388
|
+
return if documents.empty?
|
|
389
|
+
|
|
390
|
+
application = File.join(root, 'application')
|
|
391
|
+
used = {}
|
|
392
|
+
paths = documents.map do |document|
|
|
393
|
+
relative = unique_relative_path(document.fetch(:route), underscore(document.fetch(:name)), used)
|
|
394
|
+
write(File.join(application, relative), mapping_document_source(document))
|
|
395
|
+
relative
|
|
396
|
+
end
|
|
397
|
+
append_to_aggregator(File.join(application, 'application.rb'), paths)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def unique_relative_path(directory, base, used)
|
|
401
|
+
candidate = File.join(directory, "#{base}.rb")
|
|
402
|
+
suffix = 2
|
|
403
|
+
while used[candidate]
|
|
404
|
+
candidate = File.join(directory, "#{base}_#{suffix}.rb")
|
|
405
|
+
suffix += 1
|
|
406
|
+
end
|
|
407
|
+
used[candidate] = true
|
|
408
|
+
candidate
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def mapping_document_source(document)
|
|
412
|
+
<<~RUBY
|
|
413
|
+
# frozen_string_literal: true
|
|
414
|
+
|
|
415
|
+
#{native_document_declaration(document)}
|
|
416
|
+
RUBY
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def native_document_declaration(document)
|
|
420
|
+
<<~RUBY.rstrip
|
|
421
|
+
native_document #{symbol(document.fetch(:name))},
|
|
422
|
+
type: #{ruby(document.fetch(:type))},
|
|
423
|
+
unit_id: #{ruby(document.fetch(:id))},
|
|
424
|
+
container_id: #{ruby(document.fetch(:container_id))},
|
|
425
|
+
containment: #{ruby(document.fetch(:containment))},
|
|
426
|
+
deep_structure: #{native_ruby(document.fetch(:doc), 24)}
|
|
427
|
+
RUBY
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def append_to_aggregator(path, relative_paths)
|
|
431
|
+
existing = File.exist?(path) ? File.read(path).lines : []
|
|
432
|
+
additions = relative_paths.sort.map do |relative|
|
|
433
|
+
segments = relative.split(File::SEPARATOR).map { ruby(_1) }.join(", ")
|
|
434
|
+
"evaluate File.join(__dir__, #{segments})\n"
|
|
435
|
+
end
|
|
436
|
+
write(path, (existing + additions).uniq.join)
|
|
437
|
+
end
|
|
438
|
+
|
|
299
439
|
def export_menus(root, mod)
|
|
300
440
|
menus = mod.menus
|
|
301
441
|
return if menus.empty?
|
|
@@ -586,6 +726,12 @@ module Mxrb
|
|
|
586
726
|
end
|
|
587
727
|
flags = []
|
|
588
728
|
flags << " non_persistent!" unless entity.persistable
|
|
729
|
+
if oql_view_entity?(entity)
|
|
730
|
+
options = []
|
|
731
|
+
options << "source: #{ruby(entity.oql_source_document)}" if entity.oql_source_document
|
|
732
|
+
options << "query: #{ruby(entity.oql_query)}" unless entity.oql_query.to_s.empty?
|
|
733
|
+
flags << " oql_view #{options.join(', ')}" unless options.empty?
|
|
734
|
+
end
|
|
589
735
|
flags << " documentation #{ruby(entity.documentation)}" unless entity.documentation.to_s.empty?
|
|
590
736
|
generalization = entity.respond_to?(:generalization_target) ? entity.generalization_target : nil
|
|
591
737
|
flags << " generalizes #{ruby(generalization)}" if generalization
|
|
@@ -11,19 +11,26 @@ module Mxrb
|
|
|
11
11
|
class Validator
|
|
12
12
|
def initialize(path)
|
|
13
13
|
@path = path
|
|
14
|
+
@progress = Progress::NullTask.instance
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def validate
|
|
17
|
-
@
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
Progress.with("Validating #{File.basename(@path)}") do |progress|
|
|
19
|
+
@progress = progress
|
|
20
|
+
@errors = []
|
|
21
|
+
@warnings = []
|
|
22
|
+
@mpr = IO::MprFile.open(@path, readonly: true)
|
|
23
|
+
validate_tables
|
|
24
|
+
progress.advance(detail: "MPR tables")
|
|
25
|
+
validate_units
|
|
26
|
+
validate_v2_files
|
|
27
|
+
progress.advance(detail: "v2 contents")
|
|
28
|
+
Result.new(errors: @errors, warnings: @warnings)
|
|
29
|
+
end
|
|
24
30
|
rescue Error => e
|
|
25
31
|
Result.new(errors: [e.message], warnings: @warnings || [])
|
|
26
32
|
ensure
|
|
33
|
+
@progress = Progress::NullTask.instance
|
|
27
34
|
@mpr&.close
|
|
28
35
|
end
|
|
29
36
|
|
|
@@ -40,8 +47,10 @@ module Mxrb
|
|
|
40
47
|
|
|
41
48
|
def validate_units
|
|
42
49
|
@units = @mpr.all_units
|
|
50
|
+
@progress.update(total: @units.size + 3, detail: "#{@units.size} units")
|
|
43
51
|
validate_root
|
|
44
52
|
validate_unit_ids
|
|
53
|
+
@progress.advance(detail: "unit tree")
|
|
45
54
|
validate_contents
|
|
46
55
|
end
|
|
47
56
|
|
|
@@ -78,6 +87,8 @@ module Mxrb
|
|
|
78
87
|
next unless doc
|
|
79
88
|
|
|
80
89
|
validate_doc_identity(unit, doc)
|
|
90
|
+
ensure
|
|
91
|
+
@progress&.advance(detail: "unit #{unit['UnitID']}")
|
|
81
92
|
end
|
|
82
93
|
end
|
|
83
94
|
|
data/lib/mxrb/model/entity.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Mxrb
|
|
|
10
10
|
attr_accessor :id, :name, :qualified_name, :documentation,
|
|
11
11
|
:persistable, :location, :data_storage_guid,
|
|
12
12
|
:export_level, :generalization, :access_rules, :indexes,
|
|
13
|
-
:system_members
|
|
13
|
+
:system_members, :source, :oql_query, :native_type
|
|
14
14
|
|
|
15
15
|
# Build from a BSON hash (embedded in DomainModel's "entities" array).
|
|
16
16
|
def self.from_bson(doc, _domain_model_id, mpr)
|
|
@@ -19,6 +19,9 @@ module Mxrb
|
|
|
19
19
|
e.name = doc["name"] || doc["Name"]
|
|
20
20
|
e.qualified_name = doc["$QualifiedName"] || doc["\$QualifiedName"]
|
|
21
21
|
e.documentation = doc["documentation"] || doc["Documentation"] || ""
|
|
22
|
+
e.native_type = doc["$Type"]
|
|
23
|
+
e.source = doc["source"] || doc["Source"]
|
|
24
|
+
e.oql_query = doc["oqlQuery"] || doc["OqlQuery"] || doc["OQLQuery"]
|
|
22
25
|
e.data_storage_guid = doc["dataStorageGuid"] || doc["DataStorageGuid"]
|
|
23
26
|
e.export_level = doc["exportLevel"] || doc["ExportLevel"] || "Hidden"
|
|
24
27
|
e.location = parse_location(doc["location"] || doc["Location"])
|
|
@@ -58,6 +61,19 @@ module Mxrb
|
|
|
58
61
|
@generalization['generalization'] || @generalization['Generalization']
|
|
59
62
|
end
|
|
60
63
|
|
|
64
|
+
def oql_view?
|
|
65
|
+
source_type = @source.is_a?(Hash) ? @source.fetch('$Type', '') : ''
|
|
66
|
+
@native_type.to_s.match?(/ViewEntity/i) ||
|
|
67
|
+
source_type.match?(/OqlViewEntitySource/i) ||
|
|
68
|
+
!@oql_query.to_s.empty?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def oql_source_document
|
|
72
|
+
return unless @source.is_a?(Hash)
|
|
73
|
+
|
|
74
|
+
@source['SourceDocument'] || @source['sourceDocument']
|
|
75
|
+
end
|
|
76
|
+
|
|
61
77
|
def to_bson
|
|
62
78
|
{
|
|
63
79
|
"$ID" => @id,
|