mona 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.ruby-version +1 -0
  4. data/.standard.yml +3 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +193 -0
  9. data/Rakefile +8 -0
  10. data/example/Gemfile +1 -0
  11. data/example/Gemfile.lock +51 -0
  12. data/example/Monafile.rb +16 -0
  13. data/example/bounded_contexts/cart/cart_app/Package.rb +11 -0
  14. data/example/bounded_contexts/cart/cart_app/bin/console +11 -0
  15. data/example/bounded_contexts/cart/cart_app/package/cart_app.rb +2 -0
  16. data/example/bounded_contexts/cart/cart_core/Package.rb +12 -0
  17. data/example/bounded_contexts/cart/cart_core/bin/console +9 -0
  18. data/example/bounded_contexts/cart/cart_core/package/cart_core.rb +2 -0
  19. data/example/bounded_contexts/cart/cart_datasets/Package.rb +10 -0
  20. data/example/bounded_contexts/cart/cart_datasets/bin/console +9 -0
  21. data/example/bounded_contexts/cart/cart_datasets/package/cart_datasets.rb +2 -0
  22. data/example/bounded_contexts/orders/orders_app/Package.rb +12 -0
  23. data/example/bounded_contexts/orders/orders_app/bin/console +9 -0
  24. data/example/bounded_contexts/orders/orders_app/package/orders_app.rb +2 -0
  25. data/example/bounded_contexts/orders/orders_core/Package.rb +13 -0
  26. data/example/bounded_contexts/orders/orders_core/bin/console +9 -0
  27. data/example/bounded_contexts/orders/orders_core/package/orders_core.rb +2 -0
  28. data/example/bounded_contexts/orders/orders_datasets/Package.rb +11 -0
  29. data/example/bounded_contexts/orders/orders_datasets/bin/console +9 -0
  30. data/example/bounded_contexts/orders/orders_datasets/package/orders_datasets.rb +2 -0
  31. data/example/bounded_contexts/orders/orders_query_api/Package.rb +13 -0
  32. data/example/bounded_contexts/orders/orders_query_api/bin/console +9 -0
  33. data/example/bounded_contexts/orders/orders_query_api/package/cart_query_api.rb +2 -0
  34. data/example/bounded_contexts/orders/paypal_client/Package.rb +9 -0
  35. data/example/bounded_contexts/orders/paypal_client/bin/console +9 -0
  36. data/example/bounded_contexts/orders/paypal_client/package/paypal_client.rb +2 -0
  37. data/example/bounded_contexts/shared/db_connection/Package.rb +10 -0
  38. data/example/bounded_contexts/shared/db_connection/bin/console +9 -0
  39. data/example/bounded_contexts/shared/db_connection/package/db_connection.rb +2 -0
  40. data/example/bounded_contexts/shared/test_utils/Package.rb +9 -0
  41. data/example/bounded_contexts/shared/test_utils/bin/console +9 -0
  42. data/example/bounded_contexts/shared/test_utils/package/test_utils.rb +2 -0
  43. data/example/bounded_contexts/storefront/storefront_app/Package.rb +11 -0
  44. data/example/bounded_contexts/storefront/storefront_app/bin/console +9 -0
  45. data/example/bounded_contexts/storefront/storefront_app/package/storefront_app.rb +2 -0
  46. data/example/bounded_contexts/storefront/storefront_core/Package.rb +11 -0
  47. data/example/bounded_contexts/storefront/storefront_core/bin/console +9 -0
  48. data/example/bounded_contexts/storefront/storefront_core/package/storefront_core.rb +2 -0
  49. data/example/bounded_contexts/storefront/storefront_datasets/Package.rb +11 -0
  50. data/example/bounded_contexts/storefront/storefront_datasets/bin/console +9 -0
  51. data/example/bounded_contexts/storefront/storefront_datasets/package/storefront_datasets.rb +2 -0
  52. data/example/configs/cart_app/default.yml +4 -0
  53. data/example/configs/cart_app/test.yml +3 -0
  54. data/example/configs/orders_app/default.yml +4 -0
  55. data/example/configs/orders_app/test.yml +2 -0
  56. data/exe/mona +15 -0
  57. data/lib/mona/config/loader.rb +74 -0
  58. data/lib/mona/config/manager.rb +20 -0
  59. data/lib/mona/config.rb +23 -0
  60. data/lib/mona/loaders/base.rb +63 -0
  61. data/lib/mona/loaders/zeitwerk.rb +77 -0
  62. data/lib/mona/package/builder.rb +27 -0
  63. data/lib/mona/package/dependency_bypasser.rb +43 -0
  64. data/lib/mona/package/factory.rb +95 -0
  65. data/lib/mona/package/find_root.rb +33 -0
  66. data/lib/mona/package/indexer.rb +30 -0
  67. data/lib/mona/package/initializer.rb +46 -0
  68. data/lib/mona/package/list.rb +10 -0
  69. data/lib/mona/package/repo.rb +63 -0
  70. data/lib/mona/package.rb +51 -0
  71. data/lib/mona/project/builder.rb +20 -0
  72. data/lib/mona/project/factory.rb +115 -0
  73. data/lib/mona/project/find_root.rb +31 -0
  74. data/lib/mona/project/initializer.rb +26 -0
  75. data/lib/mona/project.rb +43 -0
  76. data/lib/mona/task/manager.rb +36 -0
  77. data/lib/mona/task.rb +21 -0
  78. data/lib/mona/version.rb +5 -0
  79. data/lib/mona.rb +110 -0
  80. data/mona.gemspec +54 -0
  81. data/sig/mona.rbs +4 -0
  82. metadata +296 -0
@@ -0,0 +1,63 @@
1
+ class Mona::Package::Repo
2
+ include Mona::Mixins
3
+
4
+ Inject = Mona::Deps[
5
+ "package.list",
6
+ ]
7
+
8
+ include Inject
9
+
10
+ sig do
11
+ returns(T::Array[Mona::Package])
12
+ end
13
+ def all
14
+ list.call
15
+ end
16
+
17
+ sig do
18
+ params(
19
+ name: T.nilable(
20
+ T.any(
21
+ String,
22
+ Symbol,
23
+ T::Array[
24
+ T.any(String, Symbol)
25
+ ]
26
+ )
27
+ ),
28
+ )
29
+ .returns(T::Array[Mona::Package])
30
+ end
31
+ def filter(name: nil)
32
+ packages = all
33
+
34
+ unless name.nil?
35
+ names = name.is_a?(Array) ? name : [name]
36
+ names = names.map(&:to_sym)
37
+
38
+ packages = packages.select { names.include?(_1.name) }
39
+ end
40
+
41
+ packages
42
+ end
43
+
44
+ sig do
45
+ params(
46
+ package_name: T.nilable(T.any(String, Symbol)),
47
+ )
48
+ .returns(T.nilable(Mona::Package))
49
+ end
50
+ def find(package_name)
51
+ all.detect { _1.name == package_name.to_sym }
52
+ end
53
+
54
+ sig do
55
+ params(
56
+ package_name: T.nilable(T.any(String, Symbol)),
57
+ )
58
+ .returns(Mona::Package)
59
+ end
60
+ def find!(package_name)
61
+ find(package_name) || (raise StandardError.new("package '#{package_name}' was not found for Mona project '#{Mona.current_project.root_path}'"))
62
+ end
63
+ end
@@ -0,0 +1,51 @@
1
+ class Mona::Package
2
+ include Mona::Mixins
3
+
4
+ attr_reader :name, :root_path, :dependencies, :configs
5
+
6
+ DEFAULT_ENV = :_default_
7
+
8
+ sig do
9
+ params(
10
+ name: Symbol,
11
+ root_path: String,
12
+ package_dirname: String,
13
+ dependencies: T::Hash[Symbol, Symbol]
14
+ )
15
+ .void
16
+ end
17
+ def initialize(name:, root_path:, package_dirname:, dependencies:)
18
+ @name = name
19
+ @root_path = root_path
20
+ @package_dirname = package_dirname
21
+ @dependencies = dependencies
22
+
23
+ nil
24
+ end
25
+
26
+ sig do
27
+ params(env: T.nilable(Symbol)).returns(T::Array[Symbol])
28
+ end
29
+ def get_dependencies(env = nil)
30
+ default_deps = @dependencies.fetch(DEFAULT_ENV, [])
31
+ env_deps = @dependencies.fetch(env, [])
32
+
33
+ default_deps | env_deps
34
+ end
35
+
36
+ def get_dependency_envs
37
+ @dependencies.keys
38
+ end
39
+
40
+ def workdir_path
41
+ File.join( self.root_path, @package_dirname )
42
+ end
43
+
44
+ def entrypoint_file
45
+ File.join( self.workdir_path, "#{name}.rb" )
46
+ end
47
+
48
+ def specs_path
49
+ File.join( self.root_path, '/spec/' )
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+ class Mona::Project::Builder
2
+ include Mona::Mixins
3
+
4
+ Inject = Mona::Deps[
5
+ "project.factory",
6
+ ]
7
+
8
+ include Inject
9
+
10
+ sig do
11
+ params(project_root: String).returns(Mona::Project)
12
+ end
13
+ def call(project_root)
14
+ project_file_path = "#{project_root}/#{Mona::PROJECT_FILENAME}"
15
+
16
+ project_init_proc = proc { instance_eval(File.read(project_file_path)) }
17
+
18
+ project = factory.call(project_root, init_proc: project_init_proc)
19
+ end
20
+ end
@@ -0,0 +1,115 @@
1
+ class Mona::Project::Factory
2
+ include Mona::Mixins
3
+
4
+ sig do
5
+ params(
6
+ root_path: String,
7
+ init_proc: T.proc.params(args: T.anything).returns(T.anything)
8
+ )
9
+ .returns(Mona::Project)
10
+ end
11
+ def call(root_path, init_proc:)
12
+ @tasks = []
13
+ @root_path = root_path
14
+
15
+ instance_exec(&init_proc)
16
+
17
+ project = Mona::Project.new(
18
+ root_path: root_path,
19
+ env: @env,
20
+ configs_dir: @configs_dir,
21
+ package_dirname: @package_dirname,
22
+ packages_lookup_subdir: @packages_lookup_subdir,
23
+ loader: @loader,
24
+ tasks: @tasks
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ sig do
31
+ params(
32
+ block: T.proc.params(args: T.anything).returns(T.anything)
33
+ )
34
+ .void
35
+ end
36
+ def setup(&block)
37
+ instance_exec(@project, &block)
38
+
39
+ nil
40
+ end
41
+
42
+ sig do
43
+ params(
44
+ value: T.any(Symbol, String, T.proc.returns(T.any(String, Symbol)))
45
+ )
46
+ .void
47
+ end
48
+ def set_env(value)
49
+ @env = (value.is_a?(Proc) ? value.call : value).to_sym
50
+
51
+ raise Mona::Error.new("block or value should be provided") if @env.nil?
52
+
53
+ nil
54
+ end
55
+
56
+ sig do
57
+ params(value: String).void
58
+ end
59
+ def set_configs_dir(value)
60
+ @configs_dir = value
61
+
62
+ nil
63
+ end
64
+
65
+ sig do
66
+ params(value: String).void
67
+ end
68
+ def set_package_dirname(value)
69
+ @package_dirname = value
70
+
71
+ nil
72
+ end
73
+
74
+ sig do
75
+ params(
76
+ name: Symbol,
77
+ block: T.proc.params(args: T.anything).returns(T.anything)
78
+ )
79
+ .void
80
+ end
81
+ def set_loader(name, &block)
82
+ @loader = Mona::Loaders::Base.registry.fetch(name).new(@root_path, &block)
83
+
84
+ nil
85
+ end
86
+
87
+ sig do
88
+ params(value: String).void
89
+ end
90
+ def set_packages_lookup_subdir(value)
91
+ @packages_lookup_subdir = value
92
+
93
+ nil
94
+ end
95
+
96
+ sig do
97
+ params(
98
+ name: Symbol,
99
+ on: Symbol,
100
+ block: T.proc.params(args: T.anything).returns(T.anything),
101
+ )
102
+ .void
103
+ end
104
+ def register_task(name, on:, &block)
105
+ @tasks.push(
106
+ Mona::Task.new(
107
+ name: name,
108
+ on: on,
109
+ block: block
110
+ )
111
+ )
112
+
113
+ nil
114
+ end
115
+ end
@@ -0,0 +1,31 @@
1
+ require 'zeitwerk'
2
+ require 'singleton'
3
+
4
+ class Mona::Project::FindRoot
5
+ include Mona::Mixins
6
+
7
+ SYSTEM_ROOT = '/'
8
+
9
+ sig do
10
+ params(dir: String).returns(String)
11
+ end
12
+ def call(dir)
13
+ init_dir = dir = File.expand_path(dir)
14
+
15
+ unless File.exist?(init_dir)
16
+ raise StandardError.new("path '#{init_dir}' does not exist")
17
+ end
18
+
19
+ loop do
20
+ project_file_path = File.expand_path(File.join(dir, Mona::PROJECT_FILENAME))
21
+
22
+ if File.exist?(project_file_path)
23
+ return dir
24
+ elsif dir == SYSTEM_ROOT
25
+ raise StandardError.new("#{Mona::PROJECT_FILENAME} for path '#{init_dir}' not found")
26
+ else
27
+ dir = File.expand_path("../", dir)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ class Mona::Project::Initializer
2
+ include Mona::Mixins
3
+
4
+ Inject = Mona::Deps[
5
+ "package.indexer",
6
+ "project.builder",
7
+ "project.find_root"
8
+ ]
9
+
10
+ include Inject
11
+
12
+ sig do
13
+ params(dir: String).returns(Mona::Project)
14
+ end
15
+ def call(dir)
16
+ project_root = find_root.call(dir)
17
+
18
+ project = builder.call(project_root)
19
+
20
+ project_packages = indexer.call(project.packages_lookup_subdir, project.root_path, project.package_dirname)
21
+
22
+ project.set_packages(project_packages)
23
+
24
+ project
25
+ end
26
+ end
@@ -0,0 +1,43 @@
1
+ class Mona::Project
2
+ include Mona::Mixins
3
+
4
+ attr_reader :env, :configs_dir, :package_dirname, :root_path, :packages, :loader, :tasks, :packages_lookup_subdir
5
+
6
+ DEFAULT_OPTIONS = {
7
+ configs_dir: 'configs',
8
+ package_dirname: 'package',
9
+ packages_lookup_subdir: ".",
10
+ loader: Mona::Loaders::Base,
11
+ tasks: []
12
+ }
13
+
14
+ sig do
15
+ params(
16
+ root_path: String,
17
+ env: Symbol,
18
+ loader: T.nilable(Mona::Loaders::Base),
19
+ configs_dir: T.nilable(String),
20
+ package_dirname: T.nilable(String),
21
+ packages_lookup_subdir: T.nilable(String),
22
+ tasks: T.nilable(T::Array[Mona::Task])
23
+ )
24
+ .void
25
+ end
26
+ def initialize(root_path:, env:, loader: nil, configs_dir: nil, package_dirname: nil, packages_lookup_subdir: nil, tasks: nil)
27
+ @root_path = root_path
28
+ @env = env
29
+ @packages = []
30
+
31
+ @loader = loader || DEFAULT_OPTIONS.fetch(:loader).new(@root_path)
32
+ @tasks = tasks || DEFAULT_OPTIONS.fetch(:tasks)
33
+ @configs_dir = configs_dir || DEFAULT_OPTIONS.fetch(:configs_dir)
34
+ @package_dirname = package_dirname || DEFAULT_OPTIONS.fetch(:package_dirname)
35
+ @packages_lookup_subdir = packages_lookup_subdir || DEFAULT_OPTIONS.fetch(:packages_lookup_subdir)
36
+
37
+ nil
38
+ end
39
+
40
+ def set_packages(packages)
41
+ @packages = packages
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ class Mona::Task::Manager
2
+ include Mona::Mixins
3
+
4
+ Inject = Mona::Deps[
5
+ packages_repo: "package.repo"
6
+ ]
7
+
8
+ include Inject
9
+
10
+ sig do
11
+ params(
12
+ method_name: Symbol,
13
+ args: T.anything,
14
+ kwargs: T.anything
15
+ )
16
+ .returns(T::Boolean)
17
+ end
18
+ def method_missing(method_name, *args, **kwargs)
19
+ task = Mona.current_project.tasks.detect { _1.name == method_name } || (raise StandardError.new("task '#{method_name}' not found for project"))
20
+
21
+ case task.on
22
+ when :package
23
+ statuses = kwargs[:packages].inject({}) do |result, package_name|
24
+ package = packages_repo.find!(package_name.to_sym)
25
+ result[package.name] = task.block.call(package, kwargs[:args])
26
+ result
27
+ end
28
+
29
+ failed_packages = statuses.reject {|package_name, exitstatus| exitstatus == 0}
30
+
31
+ failed_packages.any? ? false : true
32
+ else
33
+ raise StandardError.new("unsupported task subject: #{task.on}")
34
+ end
35
+ end
36
+ end
data/lib/mona/task.rb ADDED
@@ -0,0 +1,21 @@
1
+ class Mona::Task
2
+ include Mona::Mixins
3
+
4
+ attr_reader :name, :on, :block
5
+
6
+ sig do
7
+ params(
8
+ name: Symbol,
9
+ on: Symbol,
10
+ block: T.proc.params(args: T.anything).returns(T.anything),
11
+ )
12
+ .void
13
+ end
14
+ def initialize(name:, on:, block:)
15
+ @name = name.to_sym
16
+ @on = on
17
+ @block = block
18
+
19
+ nil
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mona
4
+ VERSION = "0.2.3"
5
+ end
data/lib/mona.rb ADDED
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mona/version'
4
+ require 'dry/system'
5
+ require 'benchmark'
6
+ require "zeitwerk"
7
+ require 'pry'
8
+
9
+ module Mona
10
+ class Error < StandardError; end
11
+
12
+ PROJECT_FILENAME = 'Monafile.rb'
13
+ PACKAGE_FILENAME = 'Package.rb'
14
+
15
+ module Mixins
16
+ def self.included(base)
17
+ require 'sorbet-runtime'
18
+
19
+ base.extend T::Sig
20
+ end
21
+ end
22
+
23
+ class Container < Dry::System::Container
24
+ use :env, inferrer: -> { ENV.fetch("RUBY_ENV", :production).to_sym }
25
+ # use :logging
26
+
27
+ configure do |config|
28
+ config.root = __dir__
29
+
30
+ config.component_dirs.add 'mona' do |dir|
31
+ dir.namespaces.add nil, key: nil, const: "mona"
32
+ end
33
+ end
34
+ end
35
+
36
+ Deps = Container.injector
37
+
38
+ Container.finalize! unless Container.env == :test
39
+
40
+ Zeitwerk::Loader
41
+ .for_gem
42
+ .tap(&:setup)
43
+ .tap(&:eager_load)
44
+
45
+ class << self
46
+ attr_accessor :current_project
47
+
48
+ def init_package(package_name = nil, from: caller_locations.first.path, env: nil, prevent_eager_load: false)
49
+ sync_current_project!(from) do
50
+ package_name ||= Container["package.builder"].call(from, current_project.root_path, current_project.package_dirname).name
51
+ env ||= current_project.env
52
+
53
+ Container["package.initializer"].call(package_name, env: env, prevent_eager_load: prevent_eager_load)
54
+ end
55
+ end
56
+
57
+ def root(from = caller_locations.first.path)
58
+ sync_current_project!(from) do
59
+ current_project.root_path
60
+ end
61
+ end
62
+
63
+ def tasks(from = caller_locations.first.path)
64
+ sync_current_project!(from) do
65
+ Container["task.manager"]
66
+ end
67
+ end
68
+
69
+ def loader(from = caller_locations.first.path)
70
+ sync_current_project!(from) do
71
+ current_project.loader
72
+ end
73
+ end
74
+
75
+ def configs(from = caller_locations.first.path)
76
+ sync_current_project!(from) do
77
+ Container["config.manager"]
78
+ end
79
+ end
80
+
81
+ def packages(from = caller_locations.first.path)
82
+ sync_current_project!(from) do
83
+ Container["package.repo"]
84
+ end
85
+ end
86
+
87
+ def sync_current_project!(path, &block)
88
+ path = File.expand_path(path)
89
+ path = File.dirname(path) unless File.directory?(path)
90
+
91
+ root = Container['project.find_root'].call(path)
92
+
93
+ if @current_project && @current_project.root_path == root
94
+ return block.call
95
+ end
96
+
97
+ synchronize do
98
+ @current_project = Container["project.initializer"].call(path)
99
+
100
+ block.call
101
+ end
102
+ end
103
+
104
+ def synchronize(&block)
105
+ @mutex ||= Mutex.new
106
+
107
+ @mutex.synchronize(&block)
108
+ end
109
+ end
110
+ end
data/mona.gemspec ADDED
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/mona/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mona"
7
+ spec.version = Mona::VERSION
8
+ spec.authors = ["Nikita Kononov"]
9
+ spec.email = ["vocrsz@gmail.com"]
10
+
11
+ spec.summary = "A Ruby gem for efficiently managing dependencies within a monorepository."
12
+ spec.description = <<-DESC
13
+ A Ruby gem for efficiently managing dependencies within a monorepository. \n
14
+ GitHub Repository: https://github.com/droidlabs/mono_repo_deps
15
+ DESC
16
+ spec.homepage = "https://github.com/droidlabs/mono_repo_deps"
17
+ spec.license = "MIT"
18
+ spec.required_ruby_version = ">= 2.7.0"
19
+
20
+ # spec.metadata["allowed_push_host"] = "Set to your gem server 'https://example.com'"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = "https://github.com/droidlabs/mono_repo_deps"
24
+ spec.metadata["changelog_uri"] = "https://github.com/droidlabs/mono_repo_deps"
25
+
26
+ # Specify which files should be added to the gem when it is released.
27
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
+ spec.files = Dir.chdir(__dir__) do
29
+ `git ls-files -z`.split("\x0").reject do |f|
30
+ (File.expand_path(f) == __FILE__) ||
31
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
32
+ end
33
+ end
34
+ spec.bindir = "exe"
35
+ spec.executables = spec.files.grep(/\Aexe\//) { |f| File.basename(f) }
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_dependency 'sorbet-runtime', "~> 0.5.11164"
39
+
40
+ spec.add_dependency "dry-auto_inject", "~> 1.0.1"
41
+ spec.add_dependency "dry-core", "~> 1.0.0"
42
+ spec.add_dependency "dry-configurable", "~> 1.0.1"
43
+ spec.add_dependency "dry-container", "~> 0.11.0"
44
+ spec.add_dependency "dry-system", "~> 1.0.1"
45
+
46
+ spec.add_dependency "zeitwerk", "~> 2.6.12"
47
+
48
+ spec.add_dependency "thor"
49
+
50
+ spec.add_development_dependency "bundler", "~> 2.2"
51
+ spec.add_development_dependency "rake", "~> 13.0"
52
+ spec.add_development_dependency "rspec", "~> 3.0"
53
+ spec.add_development_dependency "pry"
54
+ end
data/sig/mona.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Mona
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end