mona 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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