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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.de-DE.md +108 -0
- data/README.md +172 -0
- data/README.pt-BR.md +109 -0
- data/bin/mxrb +1613 -0
- data/docs/README.md +10 -0
- data/docs/de-DE/README.md +24 -0
- data/docs/de-DE/architectural-patterns.md +26 -0
- data/docs/de-DE/architectural-standard.md +48 -0
- data/docs/de-DE/architecture.md +76 -0
- data/docs/de-DE/compiler.md +162 -0
- data/docs/de-DE/conventions.md +19 -0
- data/docs/de-DE/design-system.md +52 -0
- data/docs/de-DE/entity-dsl.md +53 -0
- data/docs/de-DE/native-runtime-quality-report.md +74 -0
- data/docs/de-DE/oql-sql.md +211 -0
- data/docs/de-DE/platform-api-integration.md +36 -0
- data/docs/de-DE/platform-operations.md +243 -0
- data/docs/de-DE/project-structure.md +32 -0
- data/docs/de-DE/ruby-first-roadmap.md +116 -0
- data/docs/de-DE/scaffolds.md +59 -0
- data/docs/de-DE/semantic-refactoring.md +56 -0
- data/docs/de-DE/team-server.md +33 -0
- data/docs/de-DE/validation-matrix.md +64 -0
- data/docs/de-DE/vetclinic-acceptance.md +24 -0
- data/docs/de-DE/writing.md +138 -0
- data/docs/en-US/README.md +24 -0
- data/docs/en-US/architectural-patterns.md +151 -0
- data/docs/en-US/architectural-standard.md +89 -0
- data/docs/en-US/architecture.md +170 -0
- data/docs/en-US/compiler.md +157 -0
- data/docs/en-US/conventions.md +82 -0
- data/docs/en-US/design-system.md +163 -0
- data/docs/en-US/entity-dsl.md +53 -0
- data/docs/en-US/native-runtime-quality-report.md +73 -0
- data/docs/en-US/oql-sql.md +201 -0
- data/docs/en-US/platform-api-integration.md +36 -0
- data/docs/en-US/platform-operations.md +244 -0
- data/docs/en-US/project-structure.md +49 -0
- data/docs/en-US/ruby-first-roadmap.md +186 -0
- data/docs/en-US/scaffolds.md +57 -0
- data/docs/en-US/semantic-refactoring.md +198 -0
- data/docs/en-US/team-server.md +32 -0
- data/docs/en-US/validation-matrix.md +257 -0
- data/docs/en-US/vetclinic-acceptance.md +23 -0
- data/docs/en-US/writing.md +655 -0
- data/docs/pt-BR/README.md +24 -0
- data/docs/pt-BR/architectural-patterns.md +25 -0
- data/docs/pt-BR/architectural-standard.md +533 -0
- data/docs/pt-BR/architecture.md +300 -0
- data/docs/pt-BR/compiler.md +162 -0
- data/docs/pt-BR/conventions.md +19 -0
- data/docs/pt-BR/design-system.md +65 -0
- data/docs/pt-BR/entity-dsl.md +116 -0
- data/docs/pt-BR/native-runtime-quality-report.md +73 -0
- data/docs/pt-BR/oql-sql.md +208 -0
- data/docs/pt-BR/platform-api-integration.md +36 -0
- data/docs/pt-BR/platform-operations.md +260 -0
- data/docs/pt-BR/project-structure.md +32 -0
- data/docs/pt-BR/ruby-first-roadmap.md +239 -0
- data/docs/pt-BR/scaffolds.md +104 -0
- data/docs/pt-BR/semantic-refactoring.md +55 -0
- data/docs/pt-BR/team-server.md +59 -0
- data/docs/pt-BR/validation-matrix.md +86 -0
- data/docs/pt-BR/vetclinic-acceptance.md +50 -0
- data/docs/pt-BR/writing.md +177 -0
- data/examples/sudoku_evaluation.rb +14 -0
- data/examples/sudoku_functional_test.rb +28 -0
- data/lib/mxrb/architecture/graph.rb +124 -0
- data/lib/mxrb/architecture/validator.rb +98 -0
- data/lib/mxrb/benchmark.rb +33 -0
- data/lib/mxrb/compare.rb +390 -0
- data/lib/mxrb/compiler/adapter.rb +96 -0
- data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
- data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
- data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
- data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
- data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
- data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
- data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
- data/lib/mxrb/compiler/constants_materializer.rb +111 -0
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
- data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
- data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
- data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
- data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
- data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
- data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
- data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
- data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
- data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
- data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
- data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
- data/lib/mxrb/compiler/mda.rb +99 -0
- data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
- data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
- data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
- data/lib/mxrb/compiler/model_package.rb +116 -0
- data/lib/mxrb/compiler/model_values.rb +67 -0
- data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
- data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
- data/lib/mxrb/compiler/packager.rb +100 -0
- data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
- data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
- data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
- data/lib/mxrb/compiler/portable_packager.rb +356 -0
- data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
- data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
- data/lib/mxrb/compiler/project_materializer.rb +139 -0
- data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
- data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
- data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
- data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
- data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
- data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
- data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
- data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
- data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
- data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
- data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
- data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
- data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
- data/lib/mxrb/compiler/security_materializer.rb +104 -0
- data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
- data/lib/mxrb/compiler/settings_materializer.rb +25 -0
- data/lib/mxrb/compiler/source_model.rb +128 -0
- data/lib/mxrb/compiler/system_model_seed.rb +88 -0
- data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
- data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
- data/lib/mxrb/compiler/translation_materializer.rb +95 -0
- data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
- data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
- data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
- data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
- data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
- data/lib/mxrb/doctor.rb +116 -0
- data/lib/mxrb/dsl/builder.rb +1636 -0
- data/lib/mxrb/errors.rb +18 -0
- data/lib/mxrb/evaluation.rb +131 -0
- data/lib/mxrb/exporter.rb +1716 -0
- data/lib/mxrb/frontend/migrator.rb +775 -0
- data/lib/mxrb/functional.rb +307 -0
- data/lib/mxrb/github/annotator.rb +161 -0
- data/lib/mxrb/initializer.rb +257 -0
- data/lib/mxrb/integrity/validator.rb +136 -0
- data/lib/mxrb/io/bson_codec.rb +145 -0
- data/lib/mxrb/io/mpr_file.rb +558 -0
- data/lib/mxrb/io/mxunit_codec.rb +42 -0
- data/lib/mxrb/marketplace.rb +380 -0
- data/lib/mxrb/model/association.rb +98 -0
- data/lib/mxrb/model/attribute.rb +100 -0
- data/lib/mxrb/model/connector.rb +17 -0
- data/lib/mxrb/model/design_materializer.rb +133 -0
- data/lib/mxrb/model/design_migration.rb +126 -0
- data/lib/mxrb/model/design_system.rb +111 -0
- data/lib/mxrb/model/domain_model.rb +51 -0
- data/lib/mxrb/model/entity.rb +180 -0
- data/lib/mxrb/model/menu.rb +38 -0
- data/lib/mxrb/model/microflow.rb +100 -0
- data/lib/mxrb/model/module.rb +142 -0
- data/lib/mxrb/model/navigation.rb +127 -0
- data/lib/mxrb/model/page.rb +410 -0
- data/lib/mxrb/model/project.rb +187 -0
- data/lib/mxrb/model/unit.rb +48 -0
- data/lib/mxrb/module_initializer.rb +75 -0
- data/lib/mxrb/official_marketplace/content_api.rb +292 -0
- data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
- data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
- data/lib/mxrb/official_marketplace.rb +720 -0
- data/lib/mxrb/oql/analyzer.rb +155 -0
- data/lib/mxrb/oql/index_advisor.rb +85 -0
- data/lib/mxrb/oql/plan_analyzer.rb +185 -0
- data/lib/mxrb/oql/server.rb +132 -0
- data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
- data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
- data/lib/mxrb/oql/workload_analyzer.rb +175 -0
- data/lib/mxrb/oql/workload_baseline.rb +60 -0
- data/lib/mxrb/oql.rb +424 -0
- data/lib/mxrb/project_lifecycle.rb +76 -0
- data/lib/mxrb/protocols/plan.rb +86 -0
- data/lib/mxrb/protocols.rb +123 -0
- data/lib/mxrb/runtime/database_workspace.rb +677 -0
- data/lib/mxrb/runtime/docker_executor.rb +41 -0
- data/lib/mxrb/runtime/docker_workspace.rb +29 -0
- data/lib/mxrb/runtime/executor.rb +169 -0
- data/lib/mxrb/runtime/java_locator.rb +26 -0
- data/lib/mxrb/runtime/native.rb +474 -0
- data/lib/mxrb/runtime/sql_server_database.rb +146 -0
- data/lib/mxrb/runtime/toolchain.rb +82 -0
- data/lib/mxrb/scaffold/cli.rb +113 -0
- data/lib/mxrb/scaffold/generator.rb +246 -0
- data/lib/mxrb/scaffold/help.rb +80 -0
- data/lib/mxrb/scaffold/page_templates.rb +54 -0
- data/lib/mxrb/scaffold/recipes.rb +198 -0
- data/lib/mxrb/scaffold/registry.rb +82 -0
- data/lib/mxrb/scaffold/templates.rb +723 -0
- data/lib/mxrb/scaffold/transaction.rb +84 -0
- data/lib/mxrb/schema/tables.rb +55 -0
- data/lib/mxrb/semantic/analyzer.rb +473 -0
- data/lib/mxrb/semantic/batch_plan.rb +68 -0
- data/lib/mxrb/semantic/domain_mutator.rb +419 -0
- data/lib/mxrb/semantic/embedder.rb +51 -0
- data/lib/mxrb/semantic/extractor.rb +290 -0
- data/lib/mxrb/semantic/index.rb +623 -0
- data/lib/mxrb/semantic/inliner.rb +216 -0
- data/lib/mxrb/semantic/mover.rb +153 -0
- data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
- data/lib/mxrb/semantic/remover.rb +70 -0
- data/lib/mxrb/semantic/renamer.rb +211 -0
- data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
- data/lib/mxrb/semantic/vec_store.rb +75 -0
- data/lib/mxrb/team_server.rb +390 -0
- data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
- data/lib/mxrb/templates/project/11.12.1.json +32 -0
- data/lib/mxrb/templates/project/6.10.8.json +50 -0
- data/lib/mxrb/templates/project/7.17.0.json +50 -0
- data/lib/mxrb/templates/project/7.5.0.json +50 -0
- data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
- data/lib/mxrb/version.rb +5 -0
- data/lib/mxrb/widget_package.rb +338 -0
- data/lib/mxrb/widget_synchronizer.rb +31 -0
- data/lib/mxrb/writer.rb +3775 -0
- data/lib/mxrb.rb +188 -0
- data/marketplace/catalog.json +10 -0
- data/marketplace/modules/shared-kernel/domain/README.md +4 -0
- data/marketplace/modules/shared-kernel/module.rb +5 -0
- data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
- metadata +447 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Compiler
|
|
7
|
+
SecurityMaterialization = Data.define(:model_path, :metadata_path, :roles)
|
|
8
|
+
|
|
9
|
+
# Compiles project-security units into the flattened Runtime model shape.
|
|
10
|
+
class SecurityMaterializer
|
|
11
|
+
def initialize(mpr_path, deployment:)
|
|
12
|
+
@mpr_path = File.expand_path(mpr_path)
|
|
13
|
+
@deployment = File.expand_path(deployment)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def materialize
|
|
17
|
+
model_path = File.join(@deployment, 'model', 'model.mdp')
|
|
18
|
+
metadata_path = File.join(@deployment, 'model', 'metadata.json')
|
|
19
|
+
source = source_security
|
|
20
|
+
compiled, role_metadata = compile(source)
|
|
21
|
+
ModelPackage.read(model_path).upsert(compiled).write(model_path)
|
|
22
|
+
write_metadata(metadata_path, source, role_metadata)
|
|
23
|
+
SecurityMaterialization.new(model_path:, metadata_path:, roles: role_metadata.freeze)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def source_security
|
|
29
|
+
mpr = IO::MprFile.open(@mpr_path, readonly: true)
|
|
30
|
+
document = mpr.all_units.lazy.map { mpr.parse_contents(_1) }
|
|
31
|
+
.find { _1['$Type'] == 'Security$ProjectSecurity' }
|
|
32
|
+
raise CompilationError, 'MPR has no project security document' unless document
|
|
33
|
+
|
|
34
|
+
document
|
|
35
|
+
ensure
|
|
36
|
+
mpr&.close
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def compile(source)
|
|
40
|
+
source_roles = array(source['UserRoles'])
|
|
41
|
+
names = source_roles.map { _1['Name'].to_s }
|
|
42
|
+
roles = source_roles.map { compile_role(_1, names) }
|
|
43
|
+
[compiled_document(source, roles), role_metadata(source_roles, names)]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def compiled_document(source, roles)
|
|
47
|
+
{
|
|
48
|
+
'$ID' => source['$ID'], '$Type' => source['$Type'], 'UserRoles' => roles,
|
|
49
|
+
'DemoUsers' => array(source['DemoUsers']).map { compile_demo_user(_1) },
|
|
50
|
+
'PasswordPolicySettings' => source['PasswordPolicySettings'],
|
|
51
|
+
'SecurityLevel' => source['SecurityLevel'], 'AdminUserName' => source['AdminUserName'],
|
|
52
|
+
'AdminUserRole' => source['AdminUserRole'],
|
|
53
|
+
'EnableDemoUsers' => source['EnableDemoUsers'] == true,
|
|
54
|
+
'EnableGuestAccess' => source['EnableGuestAccess'] == true,
|
|
55
|
+
'GuestUserRole' => source['GuestUserRole'].to_s, 'StrictMode' => source['StrictMode'] == true
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def compile_demo_user(user)
|
|
60
|
+
user.merge('UserRoles' => array(user['UserRoles']).map(&:to_s))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def compile_role(role, names)
|
|
64
|
+
manageable = role['ManageAllRoles'] == true ? names : array(role['ManageableRoles']).map(&:to_s)
|
|
65
|
+
{
|
|
66
|
+
'$ID' => role['$ID'], '$Type' => role['$Type'], 'GUID' => role['GUID'],
|
|
67
|
+
'Name' => role['Name'], 'Description' => role['Description'].to_s,
|
|
68
|
+
'IsSystemAdministrator' => array(role['ModuleRoles']).include?('System.Administrator'),
|
|
69
|
+
'ManageableRoles' => manageable,
|
|
70
|
+
'ManageUsersWithoutRoles' => role['ManageUsersWithoutRoles'] == true,
|
|
71
|
+
'CheckSecurity' => role['CheckSecurity'] != false
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def role_metadata(roles, names)
|
|
76
|
+
ids = roles.to_h { [_1['Name'].to_s, id(_1['GUID'])] }
|
|
77
|
+
roles.to_h do |role|
|
|
78
|
+
[ids.fetch(role['Name'].to_s), metadata_role(role, names, ids)]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def metadata_role(role, names, ids)
|
|
83
|
+
manageable = role['ManageAllRoles'] == true ? names : array(role['ManageableRoles']).map(&:to_s)
|
|
84
|
+
resolved = manageable.filter_map { ids[_1] }
|
|
85
|
+
{ 'Name' => role['Name'].to_s }.tap do |value|
|
|
86
|
+
value['ManageableRoles'] = resolved unless resolved.empty?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def write_metadata(path, source, roles)
|
|
91
|
+
metadata = JSON.parse(File.read(path))
|
|
92
|
+
metadata['Roles'] = roles
|
|
93
|
+
admin = roles.find { |_role_id, value| value['Name'] == source['AdminUserRole'] }
|
|
94
|
+
raise CompilationError, "admin role #{source['AdminUserRole'].inspect} is not defined" unless admin
|
|
95
|
+
|
|
96
|
+
metadata['AdminRole'] = admin.first
|
|
97
|
+
File.write(path, JSON.pretty_generate(metadata))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def array(value) = IO::BsonCodec.parse_array(value)[:items]
|
|
101
|
+
def id(value) = IO::BsonCodec.extract_id(value)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
# Compiles project settings using the ordered, version-matched Runtime schema.
|
|
6
|
+
class SettingsDocumentCompiler
|
|
7
|
+
include ModelValues
|
|
8
|
+
|
|
9
|
+
def initialize(schema)
|
|
10
|
+
@schema = schema
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def compile(unit)
|
|
14
|
+
source = unit.document
|
|
15
|
+
unless source['$Type'] == 'Settings$ProjectSettings'
|
|
16
|
+
raise CompilationError,
|
|
17
|
+
"unsupported settings root #{source['$Type']}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
'$ID' => source['$ID'], '$Type' => source['$Type'],
|
|
22
|
+
'Settings' => array(source['Settings']).map { compile_node(_1) }
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def compile_value(value)
|
|
29
|
+
case value
|
|
30
|
+
when Hash then value['$Type'] ? compile_node(value) : value.transform_values { compile_value(_1) }
|
|
31
|
+
when Array then array(value).map { compile_value(_1) }
|
|
32
|
+
else value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def compile_node(source)
|
|
37
|
+
existing = @schema.counterpart(source)
|
|
38
|
+
@schema.fields_for(source).to_h do |field|
|
|
39
|
+
value = source.key?(field) ? compile_value(source[field]) : existing&.fetch(field, nil)
|
|
40
|
+
[field, value]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
SettingsMaterialization = Data.define(:model_path, :documents)
|
|
6
|
+
|
|
7
|
+
# Synchronizes Runtime-visible project settings, including lifecycle microflows.
|
|
8
|
+
class SettingsMaterializer
|
|
9
|
+
def initialize(mpr_path, deployment:)
|
|
10
|
+
@mpr_path = File.expand_path(mpr_path)
|
|
11
|
+
@deployment = File.expand_path(deployment)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def materialize
|
|
15
|
+
source = SourceModel.read(@mpr_path)
|
|
16
|
+
path = File.join(@deployment, 'model', 'model.mdp')
|
|
17
|
+
package = ModelPackage.read(path)
|
|
18
|
+
compiler = SettingsDocumentCompiler.new(RuntimeModelSchema.new(package, version: source.version))
|
|
19
|
+
documents = source.units_of('Settings$ProjectSettings').map { compiler.compile(_1) }
|
|
20
|
+
documents.reduce(package) { |result, document| result.upsert(document) }.write(path)
|
|
21
|
+
SettingsMaterialization.new(model_path: path, documents: documents.length)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
# Read-only indexed view of MPR units used by native materializers.
|
|
6
|
+
class SourceModel
|
|
7
|
+
Unit = Data.define(:id, :container_id, :containment, :document, :module_name)
|
|
8
|
+
CLIENT_TYPE = /\A(?:Forms|JavaScriptActions|Menus|Navigation)\$|\AMicroflows\$Nanoflow\z/
|
|
9
|
+
|
|
10
|
+
attr_reader :path, :version, :units
|
|
11
|
+
|
|
12
|
+
def self.read(path) = new(File.expand_path(path))
|
|
13
|
+
|
|
14
|
+
def initialize(path)
|
|
15
|
+
@path = path
|
|
16
|
+
read_units
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def documents(type = nil)
|
|
20
|
+
selected = type ? units.select { _1.document['$Type'] == type } : units
|
|
21
|
+
selected.map(&:document)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def units_of(type)
|
|
25
|
+
@units_by_type ||= units.group_by { _1.document['$Type'] }.transform_values(&:freeze).freeze
|
|
26
|
+
@units_by_type.fetch(type, EMPTY_UNITS)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def document_index
|
|
30
|
+
@document_index ||= {}.tap do |index|
|
|
31
|
+
units.each { index_document(_1.document, index) }
|
|
32
|
+
end.freeze
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def web_page?(unit)
|
|
36
|
+
return false if unit.document['Excluded'] == true
|
|
37
|
+
|
|
38
|
+
layout_name = unit.document.dig('FormCall', 'Form').to_s
|
|
39
|
+
layout = units_of('Forms$Layout').find do |candidate|
|
|
40
|
+
"#{candidate.module_name}.#{candidate.document['Name']}" == layout_name
|
|
41
|
+
end
|
|
42
|
+
layout.nil? || layout.document.dig('Content', '$Type') != 'Forms$NativeLayoutContent'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def web_layout?(unit)
|
|
46
|
+
unit.document['Excluded'] != true &&
|
|
47
|
+
unit.document.dig('Content', '$Type') == 'Forms$WebLayoutContent'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def optimized_web_client?
|
|
51
|
+
settings = documents('Settings$ProjectSettings').first
|
|
52
|
+
web = nested_values(settings).find { _1.is_a?(Hash) && _1['$Type'] == 'Forms$WebUIProjectSettingsPart' }
|
|
53
|
+
web&.fetch('UseOptimizedClient', nil).to_s == 'Yes'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def client_reference?(qualified_name)
|
|
57
|
+
reference = /(?:\A|[^A-Za-z0-9_.])@#{Regexp.escape(qualified_name)}(?![A-Za-z0-9_.])/
|
|
58
|
+
units.any? do |unit|
|
|
59
|
+
unit.document['$Type'].to_s.match?(CLIENT_TYPE) && deep_string_match?(unit.document, reference)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
EMPTY_UNITS = [].freeze
|
|
66
|
+
|
|
67
|
+
def read_units
|
|
68
|
+
mpr = IO::MprFile.open(path, readonly: true)
|
|
69
|
+
@version = mpr.mendix_version
|
|
70
|
+
raw_units = mpr.all_units
|
|
71
|
+
documents = raw_units.to_h { [_1['UnitID'], mpr.parse_contents(_1)] }
|
|
72
|
+
@units = build_units(raw_units, documents)
|
|
73
|
+
ensure
|
|
74
|
+
mpr&.close
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_units(raw_units, documents)
|
|
78
|
+
raw_index = raw_units.to_h { [_1['UnitID'], _1] }
|
|
79
|
+
module_names = {}
|
|
80
|
+
raw_units.map do |raw|
|
|
81
|
+
Unit.new(
|
|
82
|
+
id: raw['UnitID'], container_id: raw['ContainerID'],
|
|
83
|
+
containment: raw['ContainmentName'], document: documents.fetch(raw['UnitID']),
|
|
84
|
+
module_name: resolve_module(raw, raw_index, documents, module_names)
|
|
85
|
+
)
|
|
86
|
+
end.freeze
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def resolve_module(raw, raw_index, documents, cache)
|
|
90
|
+
id = raw['UnitID']
|
|
91
|
+
return cache[id] if cache.key?(id)
|
|
92
|
+
|
|
93
|
+
document = documents.fetch(id)
|
|
94
|
+
return cache[id] = document['Name'].to_s if document['$Type'] == 'Projects$ModuleImpl'
|
|
95
|
+
return cache[id] = nil if raw['ContainerID'] == id
|
|
96
|
+
|
|
97
|
+
parent = raw_index[raw['ContainerID']]
|
|
98
|
+
cache[id] = parent && resolve_module(parent, raw_index, documents, cache)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def deep_string_match?(value, pattern)
|
|
102
|
+
case value
|
|
103
|
+
when Hash then value.any? { |_key, child| deep_string_match?(child, pattern) }
|
|
104
|
+
when Array then value.any? { deep_string_match?(_1, pattern) }
|
|
105
|
+
else value.is_a?(String) && value.match?(pattern)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def nested_values(value)
|
|
110
|
+
case value
|
|
111
|
+
when Hash then [value, *value.values.flat_map { nested_values(_1) }]
|
|
112
|
+
when Array then value.flat_map { nested_values(_1) }
|
|
113
|
+
else []
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def index_document(value, index)
|
|
118
|
+
case value
|
|
119
|
+
when Hash
|
|
120
|
+
id = IO::BsonCodec.extract_id(value['$ID'])
|
|
121
|
+
index[id] = value if id
|
|
122
|
+
value.each_value { index_document(_1, index) }
|
|
123
|
+
when Array then value.each { index_document(_1, index) }
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'rubygems/version'
|
|
5
|
+
require 'zlib'
|
|
6
|
+
|
|
7
|
+
module Mxrb
|
|
8
|
+
module Compiler
|
|
9
|
+
# Versioned Runtime-owned System module omitted from application MPR files.
|
|
10
|
+
class SystemModelSeed
|
|
11
|
+
MODULE_ID = '6e3fe785-0e7d-42ec-a592-8bc1ea4ea87d'
|
|
12
|
+
VERSION_BANDS = [
|
|
13
|
+
['6.0.0', '7.0.0', '6.10.8'],
|
|
14
|
+
['7.0.0', '7.17.0', '7.5.0'],
|
|
15
|
+
['7.17.0', '8.0.0', '7.17.0'],
|
|
16
|
+
['9.0.0', '10.0.0', '9.6.1.29396'],
|
|
17
|
+
['10.0.0', '11.0.0', '10.24.0.73019'],
|
|
18
|
+
['11.0.0', '12.0.0', '11.12.1']
|
|
19
|
+
].map { |minimum, maximum, seed| [Gem::Version.new(minimum), Gem::Version.new(maximum), seed] }
|
|
20
|
+
.freeze
|
|
21
|
+
|
|
22
|
+
def self.for(version)
|
|
23
|
+
seed_version = seed_version_for(version)
|
|
24
|
+
path = File.join(__dir__, 'schemas', "system-model-#{seed_version}.b64")
|
|
25
|
+
raise CompilationError, "no native System model seed for Mendix #{version}" unless File.file?(path)
|
|
26
|
+
|
|
27
|
+
bytes = Zlib.gunzip(Base64.decode64(File.read(path)))
|
|
28
|
+
new(ModelPackage.decode(bytes, File.basename(path)), target_version: version, seed_version:)
|
|
29
|
+
rescue Zlib::Error, ArgumentError => e
|
|
30
|
+
raise CompilationError, "invalid System model seed for Mendix #{version}: #{e.message}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.seed_version_for(version) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
34
|
+
exact = version.to_s
|
|
35
|
+
return exact if File.file?(File.join(__dir__, 'schemas', "system-model-#{exact}.b64"))
|
|
36
|
+
unless exact.match?(/\A\d+(?:\.\d+){1,3}(?:[-+].*)?\z/)
|
|
37
|
+
raise CompilationError,
|
|
38
|
+
"invalid Mendix version #{version.inspect}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
target = Gem::Version.new(exact.scan(/\d+/).first(3).join('.'))
|
|
42
|
+
band = VERSION_BANDS.find { |minimum, maximum, _seed| target >= minimum && target < maximum }
|
|
43
|
+
return band.last if band
|
|
44
|
+
|
|
45
|
+
raise CompilationError,
|
|
46
|
+
"no audited native System model seed for Mendix #{version}; " \
|
|
47
|
+
'supported families are 6.x, 7.x, 9.x, 10.x, and 11.x'
|
|
48
|
+
rescue ArgumentError
|
|
49
|
+
raise CompilationError, "invalid Mendix version #{version.inspect}"
|
|
50
|
+
end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
51
|
+
|
|
52
|
+
attr_reader :package, :target_version, :seed_version
|
|
53
|
+
|
|
54
|
+
def initialize(package, target_version: nil, seed_version: nil)
|
|
55
|
+
@package = package
|
|
56
|
+
@target_version = target_version&.to_s
|
|
57
|
+
@seed_version = seed_version&.to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def module_reference
|
|
61
|
+
module_document = package.find(MODULE_ID)&.document
|
|
62
|
+
domain = domain_document
|
|
63
|
+
validate_module!(module_document, domain)
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
'$ID' => module_document['$ID'], '$Type' => module_document['$Type'],
|
|
67
|
+
'DomainModel' => domain.slice('$ID', '$Type'),
|
|
68
|
+
'AllDocuments' => document_references(module_document, domain)
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def domain_document
|
|
73
|
+
package.documents.find { _1['$Type'] == 'DomainModels$DomainModel' }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def document_references(module_document, domain)
|
|
77
|
+
documents = package.documents.reject { [module_document, domain].include?(_1) }
|
|
78
|
+
documents.map { _1.slice('$ID', '$Type') }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def validate_module!(module_document, domain)
|
|
82
|
+
return if module_document && domain
|
|
83
|
+
|
|
84
|
+
raise CompilationError, 'System model seed is missing its module or domain model'
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
SystemQueueMaterialization = Data.define(:model_path, :queues)
|
|
6
|
+
|
|
7
|
+
# Adds the Runtime's built-in System workflow queues absent from editor MPRs.
|
|
8
|
+
class SystemQueueMaterializer
|
|
9
|
+
QUEUES = [
|
|
10
|
+
%w[cc8b48dd-2c7e-4aa0-a614-3c683349cb72 MendixWorkflows-WorkflowExecution 5
|
|
11
|
+
3ba681ba-b94c-5f4a-82cb-d9e75947cb18],
|
|
12
|
+
%w[9d554d4e-7cf8-4a35-9504-a9f600f28e41 MendixWorkflows-DefaultTaskExecution 3
|
|
13
|
+
b06495bf-e4c6-5e62-8e98-56b7a852f440]
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
def initialize(mpr_path, deployment:)
|
|
17
|
+
@version = SourceModel.read(mpr_path).version
|
|
18
|
+
@deployment = File.expand_path(deployment)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def materialize # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
22
|
+
path = File.join(@deployment, 'model', 'model.mdp')
|
|
23
|
+
package = QUEUES.reduce(ModelPackage.read(path)) do |model, queue|
|
|
24
|
+
existing = model.documents.find { _1['QualifiedName'] == "System.#{queue[1]}" }
|
|
25
|
+
ids = if existing
|
|
26
|
+
[existing['$ID'], existing.dig('Config', '$ID')]
|
|
27
|
+
else
|
|
28
|
+
[queue[0], queue[3]]
|
|
29
|
+
end
|
|
30
|
+
model.upsert(document(ids[0], queue[1], queue[2], ids[1]))
|
|
31
|
+
end
|
|
32
|
+
package.write(path)
|
|
33
|
+
SystemQueueMaterialization.new(model_path: path, queues: QUEUES.length)
|
|
34
|
+
end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def document(id, name, parallelism, config_id)
|
|
39
|
+
{
|
|
40
|
+
'$ID' => id, '$Type' => 'Queues$Queue',
|
|
41
|
+
'Config' => queue_config(config_id, parallelism),
|
|
42
|
+
'Name' => name, 'QualifiedName' => "System.#{name}"
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def queue_config(id, parallelism)
|
|
47
|
+
config = { '$ID' => id, '$Type' => 'Queues$BasicQueueConfig' }
|
|
48
|
+
return config.merge('Parallelism' => parallelism.to_i) if @version.to_i < 10
|
|
49
|
+
|
|
50
|
+
config.merge('ParallelismExpression' => parallelism, 'ClusterWide' => false)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Compiler
|
|
5
|
+
SystemTextMaterialization = Data.define(:model_path, :texts)
|
|
6
|
+
|
|
7
|
+
# Stores the Runtime system-text index; localized values live in i18n properties.
|
|
8
|
+
class SystemTextMaterializer
|
|
9
|
+
include ModelValues
|
|
10
|
+
|
|
11
|
+
def initialize(mpr_path, deployment:)
|
|
12
|
+
@mpr_path = File.expand_path(mpr_path)
|
|
13
|
+
@deployment = File.expand_path(deployment)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def materialize
|
|
17
|
+
source = SourceModel.read(@mpr_path).units_of('Texts$SystemTextCollection').first
|
|
18
|
+
document = source ? compile(source.document) : empty_collection
|
|
19
|
+
path = File.join(@deployment, 'model', 'model.mdp')
|
|
20
|
+
ModelPackage.read(path).upsert(document).write(path)
|
|
21
|
+
SystemTextMaterialization.new(model_path: path, texts: document.fetch('SystemTexts').length)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def compile(source)
|
|
27
|
+
{
|
|
28
|
+
'$ID' => source['$ID'], '$Type' => source['$Type'],
|
|
29
|
+
'SystemTexts' => array(source['SystemTexts']).map { compile_text(_1) }
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def empty_collection
|
|
34
|
+
{
|
|
35
|
+
'$ID' => '57d2ecb5-6c2f-4ffa-a0b3-a37a40da3adf',
|
|
36
|
+
'$Type' => 'Texts$SystemTextCollection', 'SystemTexts' => []
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def compile_text(source)
|
|
41
|
+
{
|
|
42
|
+
'$ID' => source['$ID'], '$Type' => source['$Type'],
|
|
43
|
+
'Text' => source.fetch('Text').slice('$ID', '$Type'),
|
|
44
|
+
'InternalKey' => source['InternalKey']
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Compiler
|
|
7
|
+
TranslationMaterialization = Data.define(:directory, :languages, :strings, :files)
|
|
8
|
+
|
|
9
|
+
# Synchronizes all MPR Texts$Text values into Runtime Java-properties catalogs.
|
|
10
|
+
class TranslationMaterializer
|
|
11
|
+
include ModelValues
|
|
12
|
+
|
|
13
|
+
def initialize(mpr_path, deployment:)
|
|
14
|
+
@mpr_path = File.expand_path(mpr_path)
|
|
15
|
+
@deployment = File.expand_path(deployment)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def materialize
|
|
19
|
+
translations = collect(SourceModel.read(@mpr_path))
|
|
20
|
+
directory = File.join(@deployment, 'model', 'i18n')
|
|
21
|
+
FileUtils.mkdir_p(directory)
|
|
22
|
+
write_languages(directory, translations)
|
|
23
|
+
write_default(directory, translations)
|
|
24
|
+
result(directory, translations)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def collect(source)
|
|
30
|
+
result = Hash.new { |hash, language| hash[language] = {} }
|
|
31
|
+
source.documents.each { collect_value(_1, result) }
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def collect_value(value, result)
|
|
36
|
+
case value
|
|
37
|
+
when Hash
|
|
38
|
+
collect_text(value, result) if value['$Type'] == 'Texts$Text'
|
|
39
|
+
value.each_value { collect_value(_1, result) }
|
|
40
|
+
when Array then array(value).each { collect_value(_1, result) }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def collect_text(text, result)
|
|
45
|
+
key = IO::BsonCodec.extract_id(text['$ID'])
|
|
46
|
+
return unless key
|
|
47
|
+
|
|
48
|
+
array(text['Items']).each do |translation|
|
|
49
|
+
language = translation['LanguageCode'].to_s
|
|
50
|
+
result[language][key] = translation['Text'].to_s unless language.empty?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update(path, values)
|
|
55
|
+
lines = File.file?(path) ? File.readlines(path, chomp: true) : []
|
|
56
|
+
seen = {}
|
|
57
|
+
rendered = lines.map { replace_line(_1, values, seen) }
|
|
58
|
+
values.keys.sort.each { |key| rendered << property(key, values.fetch(key)) unless seen[key] }
|
|
59
|
+
File.write(path, "#{rendered.join("\n")}\n")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def replace_line(line, values, seen)
|
|
63
|
+
key = line[/\A([0-9a-f-]{36})=/i, 1]
|
|
64
|
+
return line unless key && values.key?(key)
|
|
65
|
+
|
|
66
|
+
seen[key] = true
|
|
67
|
+
property(key, values.fetch(key))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def property(key, value)
|
|
71
|
+
escaped = value.to_s.gsub('\\', '\\\\').gsub(/\r\n|\r|\n/, '\\n')
|
|
72
|
+
.gsub(/([:=#!])/) { |character| "\\#{character}" }
|
|
73
|
+
"#{key}=#{escaped}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def write_languages(directory, translations)
|
|
77
|
+
translations.each do |language, values|
|
|
78
|
+
update(File.join(directory, "translations_#{language}.properties"), values)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def write_default(directory, translations)
|
|
83
|
+
language = translations.key?('en_US') ? 'en_US' : translations.keys.min
|
|
84
|
+
update(File.join(directory, 'translations.properties'), translations.fetch(language, {}))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def result(directory, translations)
|
|
88
|
+
TranslationMaterialization.new(
|
|
89
|
+
directory:, languages: translations.keys.sort.freeze,
|
|
90
|
+
strings: translations.values.sum(&:length), files: translations.length + 1
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|