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,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Runtime
|
|
5
|
+
RUNTIME_REQUIRED_FILES = %w[launcher/runtimelauncher.jar].freeze
|
|
6
|
+
|
|
7
|
+
Plan = Data.define(
|
|
8
|
+
:mendix_version, :java_version, :toolchain_path,
|
|
9
|
+
:mx_path, :mxbuild_path, :jdk_package, :jre_package,
|
|
10
|
+
:builder_image, :runtime_image
|
|
11
|
+
) do
|
|
12
|
+
def runtime_path = File.join(toolchain_path, 'runtime')
|
|
13
|
+
|
|
14
|
+
def available?
|
|
15
|
+
RUNTIME_REQUIRED_FILES.all? { File.file?(File.join(runtime_path, _1)) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Toolchain
|
|
20
|
+
def initialize(project_path, mendix_home: nil)
|
|
21
|
+
@project_path = File.expand_path(project_path)
|
|
22
|
+
@mendix_home = mendix_home && File.expand_path(mendix_home)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def plan
|
|
26
|
+
version, java = project_metadata
|
|
27
|
+
root = toolchain_root(version)
|
|
28
|
+
Plan.new(
|
|
29
|
+
version, java, root,
|
|
30
|
+
File.join(root, "modeler", "mx"),
|
|
31
|
+
File.join(root, "modeler", "mxbuild"),
|
|
32
|
+
"zulu#{java}-ca-jdk-headless",
|
|
33
|
+
"zulu#{java}-ca-jre-headless",
|
|
34
|
+
"mxrb/mendix-builder:java#{java}",
|
|
35
|
+
"eclipse-temurin:#{java}-jre"
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def project_metadata
|
|
42
|
+
Mxrb.open(@project_path) do |project|
|
|
43
|
+
[project.mendix_version, configured_java(project) || fallback_java(project.mendix_version)]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def configured_java(project)
|
|
48
|
+
project.all_units.each do |raw|
|
|
49
|
+
doc = project.parse_bson(raw)
|
|
50
|
+
next unless doc["$Type"] == "Settings$ProjectSettings"
|
|
51
|
+
|
|
52
|
+
settings = IO::BsonCodec.parse_array(doc["Settings"])[:items]
|
|
53
|
+
model = settings.find { _1["$Type"] == "Settings$ModelSettings" }
|
|
54
|
+
value = model && model["JavaMajorVersion"].to_s
|
|
55
|
+
return value if value&.match?(/\A\d+\z/)
|
|
56
|
+
end
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def fallback_java(version)
|
|
61
|
+
major, minor, patch = version.to_s.split(".").map(&:to_i)
|
|
62
|
+
return "7" if major <= 5
|
|
63
|
+
return "8" if major <= 7
|
|
64
|
+
return "11" if major == 8
|
|
65
|
+
return "11" if major == 9 && ([minor, patch] <=> [18, 16]).negative?
|
|
66
|
+
return "17" if major == 9
|
|
67
|
+
return "21" if major >= 11 || (major == 10 && minor >= 21)
|
|
68
|
+
return "17" if major == 10 && (minor > 6 || (minor == 6 && patch >= 7))
|
|
69
|
+
|
|
70
|
+
"11"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def toolchain_root(version)
|
|
74
|
+
candidates = [
|
|
75
|
+
@mendix_home && File.join(@mendix_home, version),
|
|
76
|
+
File.join(Dir.home, ".local", "share", "mendix", version)
|
|
77
|
+
].compact
|
|
78
|
+
candidates.find { File.directory?(_1) } || candidates.first
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Scaffold
|
|
5
|
+
# Parses and renders the conventional scaffold subcommands.
|
|
6
|
+
class CLI
|
|
7
|
+
INIT_COMMANDS = %w[presentation security design ci].freeze
|
|
8
|
+
QUIET_COMMANDS = %w[ci evaluation functional-test].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(command, argv, output: $stdout)
|
|
11
|
+
@command = command
|
|
12
|
+
@argv = argv
|
|
13
|
+
@output = output
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run
|
|
17
|
+
return show_help if help?
|
|
18
|
+
return render_page_templates if page_templates?
|
|
19
|
+
|
|
20
|
+
validate_action!
|
|
21
|
+
@dry_run = @argv.delete('--dry-run') ? true : false
|
|
22
|
+
@json = @argv.delete('--json') ? true : false
|
|
23
|
+
result = generator.scaffold
|
|
24
|
+
render(result)
|
|
25
|
+
rescue ArgumentError => e
|
|
26
|
+
abort "[mxrb] error: #{e.message}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def generator
|
|
32
|
+
chain = extract_option('--chain') if @command == 'page'
|
|
33
|
+
template = extract_option('--template') if @command == 'page'
|
|
34
|
+
Generator.new(
|
|
35
|
+
kind, scaffold_name, target: target, dry_run: @dry_run,
|
|
36
|
+
page_chain: chain, page_template: template
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def page_templates? = @command == 'page' && @argv.first == 'templates'
|
|
41
|
+
|
|
42
|
+
def render_page_templates
|
|
43
|
+
@argv.shift
|
|
44
|
+
json = @argv.delete('--json')
|
|
45
|
+
abort "Unknown arguments: #{@argv.join(' ')}" unless @argv.empty?
|
|
46
|
+
|
|
47
|
+
@output.puts(json ? JSON.pretty_generate(PageTemplates.payload) : PageTemplates.tree)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def help?
|
|
51
|
+
@argv.first == '--help' || @argv.delete('--help')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def show_help
|
|
55
|
+
@output.puts Help.text(@command)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate_action!
|
|
59
|
+
action = @argv.shift
|
|
60
|
+
abort Help.text(@command) unless accepted_actions.include?(action)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def accepted_actions
|
|
64
|
+
return %w[new generate g] if @command == 'page'
|
|
65
|
+
|
|
66
|
+
[expected_action]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def expected_action
|
|
70
|
+
INIT_COMMANDS.include?(@command) ? 'init' : 'new'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def scaffold_name
|
|
74
|
+
return nil if @command == 'design'
|
|
75
|
+
|
|
76
|
+
@scaffold_name ||= @argv.shift || abort(Help.text(@command))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def target
|
|
80
|
+
value = extract_option('--target') || Dir.pwd
|
|
81
|
+
abort "Unknown arguments: #{@argv.join(' ')}" unless @argv.empty?
|
|
82
|
+
value
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def extract_option(name)
|
|
86
|
+
index = @argv.index(name)
|
|
87
|
+
return unless index
|
|
88
|
+
|
|
89
|
+
abort "#{name} requires a value" unless @argv[index + 1]
|
|
90
|
+
@argv.slice!(index, 2).last
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def kind = @command.tr('-', '_').to_sym
|
|
94
|
+
|
|
95
|
+
def render(result) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
96
|
+
if @json
|
|
97
|
+
return @output.puts JSON.pretty_generate(
|
|
98
|
+
kind: result.kind, name: result.name, dry_run: result.dry_run,
|
|
99
|
+
files: result.files, updated: result.updated
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
prefix = result.dry_run ? 'would create' : 'create'
|
|
104
|
+
result.files.each { @output.puts " #{prefix} #{_1}" }
|
|
105
|
+
result.updated.each { @output.puts " update #{_1}" }
|
|
106
|
+
return if QUIET_COMMANDS.include?(@command)
|
|
107
|
+
|
|
108
|
+
@output.puts "\nDone. Run:"
|
|
109
|
+
@output.puts ' bundle exec mxrb generate project.rb'
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Scaffold
|
|
5
|
+
# Creates conventional Ruby-first artifacts and connects missing aggregators.
|
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
|
7
|
+
class Generator
|
|
8
|
+
include Recipes
|
|
9
|
+
|
|
10
|
+
Result = Data.define(:root, :files, :updated, :kind, :name, :dry_run)
|
|
11
|
+
IDENTIFIER = /\A[A-Za-z][A-Za-z0-9_]*\z/
|
|
12
|
+
RESERVED_ENTITY_NAMES = %w[Owner ChangedBy CreatedDate ChangedDate].freeze
|
|
13
|
+
PAGE_CHAINS = %w[page:microflow page:nanoflow page:nanoflow:microflow].freeze
|
|
14
|
+
LAYER_AGGREGATORS = {
|
|
15
|
+
'domain' => 'model.rb',
|
|
16
|
+
'application' => 'application.rb',
|
|
17
|
+
'presentation' => 'presentation.rb',
|
|
18
|
+
'infrastructure' => 'infrastructure.rb'
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
def initialize(kind, name = nil, target: Dir.pwd, dry_run: false, **page_options)
|
|
22
|
+
@kind = kind.to_sym
|
|
23
|
+
@name = name.to_s
|
|
24
|
+
@root = File.expand_path(target)
|
|
25
|
+
@transaction = Transaction.new
|
|
26
|
+
@dry_run = dry_run
|
|
27
|
+
@page_chain = page_options.delete(:page_chain)
|
|
28
|
+
@page_template = page_options.delete(:page_template)
|
|
29
|
+
raise ArgumentError, "unknown generator options: #{page_options.keys.join(', ')}" unless
|
|
30
|
+
page_options.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def scaffold
|
|
34
|
+
send("scaffold_#{@kind}")
|
|
35
|
+
registry = stage_registry unless @dry_run
|
|
36
|
+
@transaction.commit unless @dry_run
|
|
37
|
+
Result.new(
|
|
38
|
+
@root, visible(@transaction.created, registry), visible(@transaction.updated, registry),
|
|
39
|
+
@kind, @name, @dry_run
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def page_chain
|
|
46
|
+
return unless @page_chain
|
|
47
|
+
return @page_chain if PAGE_CHAINS.include?(@page_chain)
|
|
48
|
+
|
|
49
|
+
raise ArgumentError,
|
|
50
|
+
"page chain must be one of: #{PAGE_CHAINS.join(', ')}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def selected_page_template
|
|
54
|
+
PageTemplates.fetch(@page_template)&.name
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def stage_registry
|
|
58
|
+
Registry.stage(
|
|
59
|
+
@transaction, root: @root, key: "#{@kind}:#{@name}", files: @transaction.created.dup
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def visible(paths, registry)
|
|
64
|
+
paths.reject { _1 == registry }.freeze
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ensure_presentation(module_name)
|
|
68
|
+
ensure_module(module_name)
|
|
69
|
+
path = module_path(module_name, 'presentation', 'presentation.rb')
|
|
70
|
+
ensure_file(path, Templates.render(:presentation, module_name:, name: nil))
|
|
71
|
+
%w[pages snippets client_actions].each do |directory|
|
|
72
|
+
ensure_file(module_path(module_name, 'presentation', directory, '.keep'), '')
|
|
73
|
+
end
|
|
74
|
+
connect_module_aggregator(module_name, 'presentation', 'presentation.rb')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def ensure_infrastructure(module_name)
|
|
78
|
+
ensure_module(module_name)
|
|
79
|
+
path = module_path(module_name, 'infrastructure', 'infrastructure.rb')
|
|
80
|
+
ensure_file(path, Templates.render(:infrastructure, module_name:, name: nil))
|
|
81
|
+
connect_module_aggregator(module_name, 'infrastructure', 'infrastructure.rb')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def connect_artifact(aggregator, *segments)
|
|
85
|
+
source = @transaction.content(aggregator)
|
|
86
|
+
raise ArgumentError, "#{aggregator}: aggregator not found" unless source
|
|
87
|
+
|
|
88
|
+
line = artifact_evaluation(source, segments)
|
|
89
|
+
return if source.include?(line)
|
|
90
|
+
|
|
91
|
+
@transaction.write(aggregator, "#{source.rstrip}\n#{line}\n")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def artifact_evaluation(source, segments)
|
|
95
|
+
return evaluate_line(segments) unless source.match?(/^\s*evaluate_dir File\.join\(__dir__, /)
|
|
96
|
+
|
|
97
|
+
%(evaluate_dir File.join(__dir__, "#{segments.fetch(0)}"))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def connect_module_aggregator(module_name, *segments)
|
|
101
|
+
path = module_path(module_name, 'module.rb')
|
|
102
|
+
source = @transaction.content(path)
|
|
103
|
+
line = evaluate_line(segments)
|
|
104
|
+
@transaction.write(path, insert_before_closing(source, line, path)) unless source.include?(line)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def connect_project_file(*segments)
|
|
108
|
+
path = project_path('project.rb')
|
|
109
|
+
source = @transaction.content(path)
|
|
110
|
+
line = evaluate_line(segments)
|
|
111
|
+
@transaction.write(path, insert_before_closing(source, line, path)) unless source.include?(line)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def connect_project_security(module_name)
|
|
115
|
+
path = project_path('project.rb')
|
|
116
|
+
source = @transaction.content(path)
|
|
117
|
+
return if source.match?(/^\s*security do\s*$/)
|
|
118
|
+
|
|
119
|
+
lines = source.lines
|
|
120
|
+
version = lines.index { _1.match?(/^\s*mendix_version\s+/) }
|
|
121
|
+
raise ArgumentError, "#{path}: mendix_version not found" unless version
|
|
122
|
+
|
|
123
|
+
block = Templates.render(:project_security, module_name:, name: nil)
|
|
124
|
+
lines.insert(version + 1, "\n#{block.lines.map { " #{_1}" }.join}")
|
|
125
|
+
@transaction.write(path, lines.join)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def insert_before_closing(source, line, path)
|
|
129
|
+
lines = source.lines
|
|
130
|
+
closing = lines.rindex { _1.strip == 'end' }
|
|
131
|
+
raise ArgumentError, "#{path}: closing end not found" unless closing
|
|
132
|
+
|
|
133
|
+
indentation = lines.filter_map { _1[/\A\s+(?=evaluate )/] }.first || ' '
|
|
134
|
+
lines.insert(closing, "#{indentation}#{line}\n")
|
|
135
|
+
lines.join
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def create_module_file(module_name, directories, artifact_name, template, suffix: nil)
|
|
139
|
+
ensure_module(module_name)
|
|
140
|
+
filename = "#{Templates.snake_case(artifact_name)}#{suffix}.rb"
|
|
141
|
+
path = module_path(module_name, *directories, filename)
|
|
142
|
+
@transaction.create(path, Templates.render(template, module_name:, name: artifact_name))
|
|
143
|
+
filename
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def create_connected_module_file(module_name, directories, artifact_name, template, suffix: nil)
|
|
147
|
+
filename = create_module_file(
|
|
148
|
+
module_name, directories, artifact_name, template, suffix:
|
|
149
|
+
)
|
|
150
|
+
layer, *relative = directories
|
|
151
|
+
aggregator = module_path(module_name, layer, LAYER_AGGREGATORS.fetch(layer))
|
|
152
|
+
connect_artifact(aggregator, *relative, filename)
|
|
153
|
+
filename
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def create_connected_module_content(module_name, directories, artifact_name, content)
|
|
157
|
+
ensure_module(module_name)
|
|
158
|
+
filename = "#{Templates.snake_case(artifact_name)}.rb"
|
|
159
|
+
path = module_path(module_name, *directories, filename)
|
|
160
|
+
@transaction.create(path, content)
|
|
161
|
+
layer, *relative = directories
|
|
162
|
+
aggregator = module_path(module_name, layer, LAYER_AGGREGATORS.fetch(layer))
|
|
163
|
+
connect_artifact(aggregator, *relative, filename)
|
|
164
|
+
filename
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def create_page_template(module_name, artifact_name, template:, chain:)
|
|
168
|
+
content = Templates.render(
|
|
169
|
+
:page_from_template, module_name:, name: artifact_name,
|
|
170
|
+
template:, refresh_action: page_refresh_action(module_name, artifact_name, chain)
|
|
171
|
+
)
|
|
172
|
+
create_connected_module_content(
|
|
173
|
+
module_name, %w[presentation pages], artifact_name, content
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def page_refresh_action(module_name, artifact_name, chain)
|
|
178
|
+
return unless chain
|
|
179
|
+
return "microflow: \"#{module_name}.ACT_Refresh#{artifact_name}\"" if
|
|
180
|
+
chain == 'page:microflow'
|
|
181
|
+
|
|
182
|
+
"nanoflow: \"#{module_name}.NAN_Refresh#{artifact_name}\""
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def create_page_navigation(module_name, artifact_name)
|
|
186
|
+
project = @transaction.content(project_path('project.rb')).to_s
|
|
187
|
+
unless project.include?('evaluate_dir File.join(__dir__, "app", "navigation", "responsive")')
|
|
188
|
+
raise ArgumentError,
|
|
189
|
+
'page chain requires the generated Responsive navigation aggregator'
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
filename = "#{Templates.snake_case(module_name)}_#{Templates.snake_case(artifact_name)}.rb"
|
|
193
|
+
@transaction.create(
|
|
194
|
+
project_path('app', 'navigation', 'responsive', filename),
|
|
195
|
+
Templates.render(:page_chain_navigation, module_name:, name: artifact_name)
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def create_project_file(directories, artifact_name, content)
|
|
200
|
+
path = project_path(*directories, "#{Templates.snake_case(artifact_name)}.rb")
|
|
201
|
+
@transaction.create(path, content)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def qualified_name
|
|
205
|
+
parts = @name.split('.', 2)
|
|
206
|
+
raise ArgumentError, 'name must be qualified as Module.Artifact' unless parts.size == 2
|
|
207
|
+
|
|
208
|
+
module_name = identifier(parts.first, label: 'module')
|
|
209
|
+
artifact_name = identifier(parts.last, label: 'artifact')
|
|
210
|
+
if @kind == :entity && RESERVED_ENTITY_NAMES.include?(artifact_name)
|
|
211
|
+
raise ArgumentError, "entity name #{artifact_name.inspect} is reserved by Mendix"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
[module_name, artifact_name]
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def module_only = identifier(@name, label: 'module')
|
|
218
|
+
|
|
219
|
+
def identifier(value, label:)
|
|
220
|
+
raise ArgumentError, "#{label} name must be a Mendix identifier" unless IDENTIFIER.match?(value)
|
|
221
|
+
|
|
222
|
+
value
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def ensure_project
|
|
226
|
+
path = project_path('project.rb')
|
|
227
|
+
raise ArgumentError, "#{path}: project.rb not found" unless File.file?(path)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def ensure_module(module_name)
|
|
231
|
+
ensure_project
|
|
232
|
+
path = module_path(module_name, 'module.rb')
|
|
233
|
+
raise ArgumentError, "#{path}: module not found" unless File.file?(path)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def project_path(*segments) = File.join(@root, *segments)
|
|
237
|
+
def module_path(module_name, *segments) = project_path('modules', module_name, *segments)
|
|
238
|
+
|
|
239
|
+
def evaluate_line(segments)
|
|
240
|
+
quoted = segments.map { |segment| %("#{segment}") }.join(', ')
|
|
241
|
+
"evaluate File.join(__dir__, #{quoted})"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
# rubocop:enable Metrics/ClassLength
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Scaffold
|
|
5
|
+
# Human-readable command catalog used by both top-level and subcommand help.
|
|
6
|
+
module Help
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
COMMANDS = {
|
|
10
|
+
'entity' => ['new Module.Entity', 'Create a domain entity file', 'domain/entities'],
|
|
11
|
+
'enumeration' => ['new Module.Enum', 'Create an enumeration', 'domain/enumerations'],
|
|
12
|
+
'use-case' => ['new Module.Flow', 'Create an application use case', 'application/use_cases'],
|
|
13
|
+
'validation' => ['new Module.Flow', 'Create a validation flow', 'application/validations'],
|
|
14
|
+
'query' => ['new Module.Flow', 'Create a query flow', 'application/queries'],
|
|
15
|
+
'functional-test' => ['new Module.Flow', 'Create a runtime test', 'functional_tests'],
|
|
16
|
+
'evaluation' => ['new Name', 'Create static model checks', 'evaluations'],
|
|
17
|
+
'presentation' => ['init Module', 'Initialize presentation directories', 'presentation'],
|
|
18
|
+
'page' => [
|
|
19
|
+
'new|generate|g Module.Page [--template NAME] [--chain CHAIN] | templates [--json]',
|
|
20
|
+
'Create a page or an executable page-led vertical slice',
|
|
21
|
+
'presentation or domain/application/presentation/navigation'
|
|
22
|
+
],
|
|
23
|
+
'nanoflow' => ['new Module.Flow', 'Create a client nanoflow', 'presentation/client_actions'],
|
|
24
|
+
'repository' => ['new Module.Repository', 'Create repository port and adapter', 'application/infrastructure'],
|
|
25
|
+
'security' => ['init Module', 'Create module roles template', 'security'],
|
|
26
|
+
'scheduled-event' => ['new Module.Event', 'Create scheduled event and handler', 'application/jobs'],
|
|
27
|
+
'constant' => ['new Module.Constant', 'Create a string constant', 'domain/constants'],
|
|
28
|
+
'integration' => ['new Module.Adapter', 'Create integration adapter flow', 'infrastructure/integrations'],
|
|
29
|
+
'published-rest' => ['new Module.Handler', 'Create published REST handler flow', 'infrastructure/endpoints'],
|
|
30
|
+
'consumed-rest' => ['new Module.Client', 'Create consumed REST adapter flow', 'infrastructure/integrations'],
|
|
31
|
+
'java-action' => ['new Module.Adapter', 'Create Java Action adapter flow', 'infrastructure/actions'],
|
|
32
|
+
'design' => ['init', 'Initialize the project design system', 'app/design_system'],
|
|
33
|
+
'ci' => ['init github', 'Create a GitHub Actions workflow', '.github/workflows']
|
|
34
|
+
}.freeze
|
|
35
|
+
OPTIONS = <<~HELP
|
|
36
|
+
Options:
|
|
37
|
+
--target DIR Project root (default: current directory)
|
|
38
|
+
--dry-run Preview paths and aggregator updates without writing
|
|
39
|
+
--json Render the result as JSON
|
|
40
|
+
--help Show this help
|
|
41
|
+
|
|
42
|
+
Documentation:
|
|
43
|
+
https://github.com/lucamykael/mxrb/blob/main/docs/pt-BR/scaffolds.md
|
|
44
|
+
HELP
|
|
45
|
+
PAGE_OPTIONS = <<~HELP
|
|
46
|
+
Page chains:
|
|
47
|
+
--chain page:microflow Page calls a microflow directly
|
|
48
|
+
--chain page:nanoflow Page calls a client nanoflow
|
|
49
|
+
--chain page:nanoflow:microflow Page calls a nanoflow that calls a microflow
|
|
50
|
+
|
|
51
|
+
Without --chain, creates the minimal page scaffold.
|
|
52
|
+
|
|
53
|
+
Page templates:
|
|
54
|
+
mxrb page templates [--json]
|
|
55
|
+
--template starter|blank|dashboard|form-vertical
|
|
56
|
+
HELP
|
|
57
|
+
|
|
58
|
+
def text(command)
|
|
59
|
+
usage, description, destination = COMMANDS.fetch(command)
|
|
60
|
+
<<~HELP
|
|
61
|
+
Usage: mxrb #{command} #{usage} [--target DIR]
|
|
62
|
+
|
|
63
|
+
#{description}.
|
|
64
|
+
Destination: #{destination}
|
|
65
|
+
Existing files are never overwritten. Aggregators are connected automatically.
|
|
66
|
+
|
|
67
|
+
#{page_options(command)}
|
|
68
|
+
|
|
69
|
+
#{OPTIONS}
|
|
70
|
+
HELP
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def page_options(command)
|
|
74
|
+
return '' unless command == 'page'
|
|
75
|
+
|
|
76
|
+
PAGE_OPTIONS
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Scaffold
|
|
5
|
+
# Audited page patterns that become ordinary editable Mendix pages.
|
|
6
|
+
module PageTemplates
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
Entry = Data.define(:category, :name, :description, :data_backed)
|
|
10
|
+
ENTRIES = [
|
|
11
|
+
Entry.new('General', 'starter', 'Title and page header', false),
|
|
12
|
+
Entry.new('General', 'blank', 'Empty responsive content area', false),
|
|
13
|
+
Entry.new('Dashboards', 'dashboard', 'Header and three responsive cards', false),
|
|
14
|
+
Entry.new('Forms', 'form-vertical', 'DataView with vertical inputs and actions', true)
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
def fetch(name)
|
|
18
|
+
return unless name
|
|
19
|
+
|
|
20
|
+
ENTRIES.find { _1.name == name.to_s } || raise(
|
|
21
|
+
ArgumentError, "page template must be one of: #{ENTRIES.map(&:name).join(', ')}"
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def tree
|
|
26
|
+
groups = ENTRIES.group_by(&:category).to_a
|
|
27
|
+
lines = ['Page templates'] + groups.flat_map.with_index do |group, index|
|
|
28
|
+
category_lines(group, last: index == groups.size - 1)
|
|
29
|
+
end
|
|
30
|
+
lines.join("\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def category_lines(group, last:)
|
|
34
|
+
category, entries = group
|
|
35
|
+
heading = "#{last ? '└──' : '├──'} #{category}"
|
|
36
|
+
children = entries.map.with_index do |entry, index|
|
|
37
|
+
branch = index == entries.size - 1 ? '└──' : '├──'
|
|
38
|
+
"#{last ? ' ' : '│ '}#{branch} #{entry.name} — #{entry.description}"
|
|
39
|
+
end
|
|
40
|
+
[heading, *children]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def payload
|
|
44
|
+
ENTRIES.group_by(&:category).map do |category, entries|
|
|
45
|
+
{
|
|
46
|
+
category:, templates: entries.map do |entry|
|
|
47
|
+
{ name: entry.name, description: entry.description, data_backed: entry.data_backed }
|
|
48
|
+
end
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|