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.
Files changed (237) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.de-DE.md +108 -0
  4. data/README.md +172 -0
  5. data/README.pt-BR.md +109 -0
  6. data/bin/mxrb +1613 -0
  7. data/docs/README.md +10 -0
  8. data/docs/de-DE/README.md +24 -0
  9. data/docs/de-DE/architectural-patterns.md +26 -0
  10. data/docs/de-DE/architectural-standard.md +48 -0
  11. data/docs/de-DE/architecture.md +76 -0
  12. data/docs/de-DE/compiler.md +162 -0
  13. data/docs/de-DE/conventions.md +19 -0
  14. data/docs/de-DE/design-system.md +52 -0
  15. data/docs/de-DE/entity-dsl.md +53 -0
  16. data/docs/de-DE/native-runtime-quality-report.md +74 -0
  17. data/docs/de-DE/oql-sql.md +211 -0
  18. data/docs/de-DE/platform-api-integration.md +36 -0
  19. data/docs/de-DE/platform-operations.md +243 -0
  20. data/docs/de-DE/project-structure.md +32 -0
  21. data/docs/de-DE/ruby-first-roadmap.md +116 -0
  22. data/docs/de-DE/scaffolds.md +59 -0
  23. data/docs/de-DE/semantic-refactoring.md +56 -0
  24. data/docs/de-DE/team-server.md +33 -0
  25. data/docs/de-DE/validation-matrix.md +64 -0
  26. data/docs/de-DE/vetclinic-acceptance.md +24 -0
  27. data/docs/de-DE/writing.md +138 -0
  28. data/docs/en-US/README.md +24 -0
  29. data/docs/en-US/architectural-patterns.md +151 -0
  30. data/docs/en-US/architectural-standard.md +89 -0
  31. data/docs/en-US/architecture.md +170 -0
  32. data/docs/en-US/compiler.md +157 -0
  33. data/docs/en-US/conventions.md +82 -0
  34. data/docs/en-US/design-system.md +163 -0
  35. data/docs/en-US/entity-dsl.md +53 -0
  36. data/docs/en-US/native-runtime-quality-report.md +73 -0
  37. data/docs/en-US/oql-sql.md +201 -0
  38. data/docs/en-US/platform-api-integration.md +36 -0
  39. data/docs/en-US/platform-operations.md +244 -0
  40. data/docs/en-US/project-structure.md +49 -0
  41. data/docs/en-US/ruby-first-roadmap.md +186 -0
  42. data/docs/en-US/scaffolds.md +57 -0
  43. data/docs/en-US/semantic-refactoring.md +198 -0
  44. data/docs/en-US/team-server.md +32 -0
  45. data/docs/en-US/validation-matrix.md +257 -0
  46. data/docs/en-US/vetclinic-acceptance.md +23 -0
  47. data/docs/en-US/writing.md +655 -0
  48. data/docs/pt-BR/README.md +24 -0
  49. data/docs/pt-BR/architectural-patterns.md +25 -0
  50. data/docs/pt-BR/architectural-standard.md +533 -0
  51. data/docs/pt-BR/architecture.md +300 -0
  52. data/docs/pt-BR/compiler.md +162 -0
  53. data/docs/pt-BR/conventions.md +19 -0
  54. data/docs/pt-BR/design-system.md +65 -0
  55. data/docs/pt-BR/entity-dsl.md +116 -0
  56. data/docs/pt-BR/native-runtime-quality-report.md +73 -0
  57. data/docs/pt-BR/oql-sql.md +208 -0
  58. data/docs/pt-BR/platform-api-integration.md +36 -0
  59. data/docs/pt-BR/platform-operations.md +260 -0
  60. data/docs/pt-BR/project-structure.md +32 -0
  61. data/docs/pt-BR/ruby-first-roadmap.md +239 -0
  62. data/docs/pt-BR/scaffolds.md +104 -0
  63. data/docs/pt-BR/semantic-refactoring.md +55 -0
  64. data/docs/pt-BR/team-server.md +59 -0
  65. data/docs/pt-BR/validation-matrix.md +86 -0
  66. data/docs/pt-BR/vetclinic-acceptance.md +50 -0
  67. data/docs/pt-BR/writing.md +177 -0
  68. data/examples/sudoku_evaluation.rb +14 -0
  69. data/examples/sudoku_functional_test.rb +28 -0
  70. data/lib/mxrb/architecture/graph.rb +124 -0
  71. data/lib/mxrb/architecture/validator.rb +98 -0
  72. data/lib/mxrb/benchmark.rb +33 -0
  73. data/lib/mxrb/compare.rb +390 -0
  74. data/lib/mxrb/compiler/adapter.rb +96 -0
  75. data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
  76. data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
  77. data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
  78. data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
  79. data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
  80. data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
  81. data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
  82. data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
  83. data/lib/mxrb/compiler/constants_materializer.rb +111 -0
  84. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
  85. data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
  86. data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
  87. data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
  88. data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
  89. data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
  90. data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
  91. data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
  92. data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
  93. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
  94. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
  95. data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
  96. data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
  97. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
  98. data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
  99. data/lib/mxrb/compiler/mda.rb +99 -0
  100. data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
  101. data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
  102. data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
  103. data/lib/mxrb/compiler/model_package.rb +116 -0
  104. data/lib/mxrb/compiler/model_values.rb +67 -0
  105. data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
  106. data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
  107. data/lib/mxrb/compiler/packager.rb +100 -0
  108. data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
  109. data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
  110. data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
  111. data/lib/mxrb/compiler/portable_packager.rb +356 -0
  112. data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
  113. data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
  114. data/lib/mxrb/compiler/project_materializer.rb +139 -0
  115. data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
  116. data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
  117. data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
  118. data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
  119. data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
  120. data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
  121. data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
  122. data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
  123. data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
  124. data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
  125. data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
  126. data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
  127. data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
  128. data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
  129. data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
  130. data/lib/mxrb/compiler/security_materializer.rb +104 -0
  131. data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
  132. data/lib/mxrb/compiler/settings_materializer.rb +25 -0
  133. data/lib/mxrb/compiler/source_model.rb +128 -0
  134. data/lib/mxrb/compiler/system_model_seed.rb +88 -0
  135. data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
  136. data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
  137. data/lib/mxrb/compiler/translation_materializer.rb +95 -0
  138. data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
  139. data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
  140. data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
  141. data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
  142. data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
  143. data/lib/mxrb/doctor.rb +116 -0
  144. data/lib/mxrb/dsl/builder.rb +1636 -0
  145. data/lib/mxrb/errors.rb +18 -0
  146. data/lib/mxrb/evaluation.rb +131 -0
  147. data/lib/mxrb/exporter.rb +1716 -0
  148. data/lib/mxrb/frontend/migrator.rb +775 -0
  149. data/lib/mxrb/functional.rb +307 -0
  150. data/lib/mxrb/github/annotator.rb +161 -0
  151. data/lib/mxrb/initializer.rb +257 -0
  152. data/lib/mxrb/integrity/validator.rb +136 -0
  153. data/lib/mxrb/io/bson_codec.rb +145 -0
  154. data/lib/mxrb/io/mpr_file.rb +558 -0
  155. data/lib/mxrb/io/mxunit_codec.rb +42 -0
  156. data/lib/mxrb/marketplace.rb +380 -0
  157. data/lib/mxrb/model/association.rb +98 -0
  158. data/lib/mxrb/model/attribute.rb +100 -0
  159. data/lib/mxrb/model/connector.rb +17 -0
  160. data/lib/mxrb/model/design_materializer.rb +133 -0
  161. data/lib/mxrb/model/design_migration.rb +126 -0
  162. data/lib/mxrb/model/design_system.rb +111 -0
  163. data/lib/mxrb/model/domain_model.rb +51 -0
  164. data/lib/mxrb/model/entity.rb +180 -0
  165. data/lib/mxrb/model/menu.rb +38 -0
  166. data/lib/mxrb/model/microflow.rb +100 -0
  167. data/lib/mxrb/model/module.rb +142 -0
  168. data/lib/mxrb/model/navigation.rb +127 -0
  169. data/lib/mxrb/model/page.rb +410 -0
  170. data/lib/mxrb/model/project.rb +187 -0
  171. data/lib/mxrb/model/unit.rb +48 -0
  172. data/lib/mxrb/module_initializer.rb +75 -0
  173. data/lib/mxrb/official_marketplace/content_api.rb +292 -0
  174. data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
  175. data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
  176. data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
  177. data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
  178. data/lib/mxrb/official_marketplace.rb +720 -0
  179. data/lib/mxrb/oql/analyzer.rb +155 -0
  180. data/lib/mxrb/oql/index_advisor.rb +85 -0
  181. data/lib/mxrb/oql/plan_analyzer.rb +185 -0
  182. data/lib/mxrb/oql/server.rb +132 -0
  183. data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
  184. data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
  185. data/lib/mxrb/oql/workload_analyzer.rb +175 -0
  186. data/lib/mxrb/oql/workload_baseline.rb +60 -0
  187. data/lib/mxrb/oql.rb +424 -0
  188. data/lib/mxrb/project_lifecycle.rb +76 -0
  189. data/lib/mxrb/protocols/plan.rb +86 -0
  190. data/lib/mxrb/protocols.rb +123 -0
  191. data/lib/mxrb/runtime/database_workspace.rb +677 -0
  192. data/lib/mxrb/runtime/docker_executor.rb +41 -0
  193. data/lib/mxrb/runtime/docker_workspace.rb +29 -0
  194. data/lib/mxrb/runtime/executor.rb +169 -0
  195. data/lib/mxrb/runtime/java_locator.rb +26 -0
  196. data/lib/mxrb/runtime/native.rb +474 -0
  197. data/lib/mxrb/runtime/sql_server_database.rb +146 -0
  198. data/lib/mxrb/runtime/toolchain.rb +82 -0
  199. data/lib/mxrb/scaffold/cli.rb +113 -0
  200. data/lib/mxrb/scaffold/generator.rb +246 -0
  201. data/lib/mxrb/scaffold/help.rb +80 -0
  202. data/lib/mxrb/scaffold/page_templates.rb +54 -0
  203. data/lib/mxrb/scaffold/recipes.rb +198 -0
  204. data/lib/mxrb/scaffold/registry.rb +82 -0
  205. data/lib/mxrb/scaffold/templates.rb +723 -0
  206. data/lib/mxrb/scaffold/transaction.rb +84 -0
  207. data/lib/mxrb/schema/tables.rb +55 -0
  208. data/lib/mxrb/semantic/analyzer.rb +473 -0
  209. data/lib/mxrb/semantic/batch_plan.rb +68 -0
  210. data/lib/mxrb/semantic/domain_mutator.rb +419 -0
  211. data/lib/mxrb/semantic/embedder.rb +51 -0
  212. data/lib/mxrb/semantic/extractor.rb +290 -0
  213. data/lib/mxrb/semantic/index.rb +623 -0
  214. data/lib/mxrb/semantic/inliner.rb +216 -0
  215. data/lib/mxrb/semantic/mover.rb +153 -0
  216. data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
  217. data/lib/mxrb/semantic/remover.rb +70 -0
  218. data/lib/mxrb/semantic/renamer.rb +211 -0
  219. data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
  220. data/lib/mxrb/semantic/vec_store.rb +75 -0
  221. data/lib/mxrb/team_server.rb +390 -0
  222. data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
  223. data/lib/mxrb/templates/project/11.12.1.json +32 -0
  224. data/lib/mxrb/templates/project/6.10.8.json +50 -0
  225. data/lib/mxrb/templates/project/7.17.0.json +50 -0
  226. data/lib/mxrb/templates/project/7.5.0.json +50 -0
  227. data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
  228. data/lib/mxrb/version.rb +5 -0
  229. data/lib/mxrb/widget_package.rb +338 -0
  230. data/lib/mxrb/widget_synchronizer.rb +31 -0
  231. data/lib/mxrb/writer.rb +3775 -0
  232. data/lib/mxrb.rb +188 -0
  233. data/marketplace/catalog.json +10 -0
  234. data/marketplace/modules/shared-kernel/domain/README.md +4 -0
  235. data/marketplace/modules/shared-kernel/module.rb +5 -0
  236. data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
  237. 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