lepus 0.0.1.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/specs.yml +44 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +35 -0
  6. data/.tool-versions +1 -0
  7. data/CHANGELOG.md +10 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +120 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +213 -0
  12. data/Rakefile +4 -0
  13. data/bin/console +9 -0
  14. data/bin/setup +7 -0
  15. data/docker-compose.yml +8 -0
  16. data/exec/lepus +9 -0
  17. data/gemfiles/rails52.gemfile +5 -0
  18. data/gemfiles/rails52.gemfile.lock +242 -0
  19. data/gemfiles/rails61.gemfile +5 -0
  20. data/gemfiles/rails61.gemfile.lock +260 -0
  21. data/lepus.gemspec +53 -0
  22. data/lib/lepus/app_executor.rb +19 -0
  23. data/lib/lepus/cli.rb +27 -0
  24. data/lib/lepus/configuration.rb +90 -0
  25. data/lib/lepus/consumer.rb +177 -0
  26. data/lib/lepus/consumer_config.rb +149 -0
  27. data/lib/lepus/consumer_wrapper.rb +46 -0
  28. data/lib/lepus/lifecycle_hooks.rb +49 -0
  29. data/lib/lepus/message.rb +37 -0
  30. data/lib/lepus/middleware.rb +18 -0
  31. data/lib/lepus/middlewares/honeybadger.rb +23 -0
  32. data/lib/lepus/middlewares/json.rb +35 -0
  33. data/lib/lepus/middlewares/max_retry.rb +57 -0
  34. data/lib/lepus/primitive/string.rb +55 -0
  35. data/lib/lepus/process.rb +136 -0
  36. data/lib/lepus/process_registry.rb +37 -0
  37. data/lib/lepus/processes/base.rb +50 -0
  38. data/lib/lepus/processes/callbacks.rb +72 -0
  39. data/lib/lepus/processes/consumer.rb +113 -0
  40. data/lib/lepus/processes/interruptible.rb +38 -0
  41. data/lib/lepus/processes/procline.rb +11 -0
  42. data/lib/lepus/processes/registrable.rb +67 -0
  43. data/lib/lepus/processes/runnable.rb +102 -0
  44. data/lib/lepus/processes/supervised.rb +44 -0
  45. data/lib/lepus/processes.rb +6 -0
  46. data/lib/lepus/producer.rb +42 -0
  47. data/lib/lepus/rails/log_subscriber.rb +120 -0
  48. data/lib/lepus/rails/railtie.rb +31 -0
  49. data/lib/lepus/rails.rb +7 -0
  50. data/lib/lepus/supervisor/config.rb +45 -0
  51. data/lib/lepus/supervisor/maintenance.rb +35 -0
  52. data/lib/lepus/supervisor/pidfile.rb +61 -0
  53. data/lib/lepus/supervisor/pidfiled.rb +29 -0
  54. data/lib/lepus/supervisor/signals.rb +71 -0
  55. data/lib/lepus/supervisor.rb +204 -0
  56. data/lib/lepus/timer.rb +29 -0
  57. data/lib/lepus/version.rb +5 -0
  58. data/lib/lepus.rb +95 -0
  59. data/lib/puma/plugin/lepus.rb +74 -0
  60. metadata +290 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aecb8b8247c618b92702f4d38ddd12662dbb5e8d4b1c18782796ab7caf790c6b
4
+ data.tar.gz: 3da8d288fad18c9174db8e18c989e1f6e861d0ced3116e6ea20c6ce892d80f53
5
+ SHA512:
6
+ metadata.gz: eb553b2b3b980b9d64c5a04f9c4c56634003e7f3e850b17a2437343ebc9834019d5778673e891ce2f0ec8abec02b2f54155fbf5b3c9766a80348497139a47995
7
+ data.tar.gz: cbea89d36fffc882720d0f148549c39a5f0d7211ff38035218e387549c14d2a716012ddafa616599c4e819a6346ccb5b9ed794c8b73369aaf5e223af6dc0449e
@@ -0,0 +1,44 @@
1
+ name: Specs
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby: ['2.7', '3.0']
16
+ gemfile:
17
+ - Gemfile
18
+ - gemfiles/rails61.gemfile
19
+ name: ${{ matrix.ruby }}-${{ matrix.gemfile }}
20
+ env:
21
+ BUNDLE_GEMFILE: ${{ matrix.gemfile }}
22
+ services:
23
+ rabbitmq:
24
+ image: rabbitmq:3-management
25
+ options: >-
26
+ --health-cmd "rabbitmq-diagnostics ping"
27
+ --health-interval 10s
28
+ --health-timeout 5s
29
+ --health-retries 5
30
+ ports:
31
+ - 5672:5672
32
+ - 15672:15672
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: ${{ matrix.ruby }}
38
+ bundler-cache: true
39
+ - name: Install dependencies
40
+ run: bundle install
41
+ - name: Run tests
42
+ run: bundle exec rspec
43
+ env:
44
+ RABBITMQ_URL: amqp://localhost:5672
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.env
3
+ /.rspec_status
4
+ /.yardoc
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ *.gem
12
+ /app/consumers/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,35 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+
5
+ require:
6
+ - rubocop-performance
7
+ - rubocop-rspec
8
+ - standard/cop/block_single_line_braces
9
+
10
+ inherit_gem:
11
+ standard: config/base.yml
12
+
13
+ AllCops:
14
+ TargetRubyVersion: 2.5
15
+ SuggestExtensions: false
16
+ Exclude:
17
+ - "db/**/*"
18
+ - "tmp/**/*"
19
+ - "vendor/**/*"
20
+ NewCops: enable
21
+
22
+ RSpec/MultipleExpectations:
23
+ Enabled: false
24
+
25
+ RSpec/ExampleLength:
26
+ Enabled: false
27
+
28
+ RSpec/MultipleMemoizedHelpers:
29
+ Enabled: false
30
+
31
+ RSpec/MessageSpies:
32
+ Enabled: false
33
+
34
+ RSpec/StubbedMock:
35
+ Enabled: false
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 2.7.8
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 0.0.1 - 2024-08-01
8
+ The first release of the gem
9
+ * Added: Initial implementation with support to Sidekiq and Faktory backends
10
+ * UniqueJob middleware to avoid duplicated jobs
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in lepus.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,120 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lepus (0.0.1.beta2)
5
+ bunny
6
+ concurrent-ruby
7
+ multi_json
8
+ thor
9
+ zeitwerk
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ addressable (2.8.7)
15
+ public_suffix (>= 2.0.2, < 7.0)
16
+ amq-protocol (2.3.2)
17
+ ast (2.4.2)
18
+ bigdecimal (3.1.8)
19
+ bunny (2.23.0)
20
+ amq-protocol (~> 2.3, >= 2.3.1)
21
+ sorted_set (~> 1, >= 1.0.2)
22
+ coderay (1.1.3)
23
+ concurrent-ruby (1.3.5)
24
+ crack (1.0.0)
25
+ bigdecimal
26
+ rexml
27
+ diff-lcs (1.5.1)
28
+ dotenv (2.8.1)
29
+ hashdiff (1.1.1)
30
+ json (2.7.5)
31
+ language_server-protocol (3.17.0.3)
32
+ lint_roller (1.1.0)
33
+ method_source (1.1.0)
34
+ multi_json (1.15.0)
35
+ parallel (1.26.3)
36
+ parser (3.3.5.0)
37
+ ast (~> 2.4.1)
38
+ racc
39
+ pry (0.14.2)
40
+ coderay (~> 1.1)
41
+ method_source (~> 1.0)
42
+ public_suffix (5.1.1)
43
+ racc (1.8.1)
44
+ rainbow (3.1.1)
45
+ rbtree (0.4.6)
46
+ regexp_parser (2.9.2)
47
+ rexml (3.3.9)
48
+ rspec (3.13.0)
49
+ rspec-core (~> 3.13.0)
50
+ rspec-expectations (~> 3.13.0)
51
+ rspec-mocks (~> 3.13.0)
52
+ rspec-core (3.13.2)
53
+ rspec-support (~> 3.13.0)
54
+ rspec-expectations (3.13.3)
55
+ diff-lcs (>= 1.2.0, < 2.0)
56
+ rspec-support (~> 3.13.0)
57
+ rspec-mocks (3.13.2)
58
+ diff-lcs (>= 1.2.0, < 2.0)
59
+ rspec-support (~> 3.13.0)
60
+ rspec-support (3.13.1)
61
+ rubocop (1.64.1)
62
+ json (~> 2.3)
63
+ language_server-protocol (>= 3.17.0)
64
+ parallel (~> 1.10)
65
+ parser (>= 3.3.0.2)
66
+ rainbow (>= 2.2.2, < 4.0)
67
+ regexp_parser (>= 1.8, < 3.0)
68
+ rexml (>= 3.2.5, < 4.0)
69
+ rubocop-ast (>= 1.31.1, < 2.0)
70
+ ruby-progressbar (~> 1.7)
71
+ unicode-display_width (>= 2.4.0, < 3.0)
72
+ rubocop-ast (1.33.0)
73
+ parser (>= 3.3.1.0)
74
+ rubocop-performance (1.21.1)
75
+ rubocop (>= 1.48.1, < 2.0)
76
+ rubocop-ast (>= 1.31.1, < 2.0)
77
+ rubocop-rspec (3.2.0)
78
+ rubocop (~> 1.61)
79
+ ruby-progressbar (1.13.0)
80
+ set (1.0.4)
81
+ sorted_set (1.0.3)
82
+ rbtree
83
+ set (~> 1.0)
84
+ standard (1.37.0)
85
+ language_server-protocol (~> 3.17.0.2)
86
+ lint_roller (~> 1.0)
87
+ rubocop (~> 1.64.0)
88
+ standard-custom (~> 1.0.0)
89
+ standard-performance (~> 1.4)
90
+ standard-custom (1.0.2)
91
+ lint_roller (~> 1.0)
92
+ rubocop (~> 1.50)
93
+ standard-performance (1.4.0)
94
+ lint_roller (~> 1.1)
95
+ rubocop-performance (~> 1.21.0)
96
+ thor (1.3.2)
97
+ unicode-display_width (2.6.0)
98
+ webmock (3.24.0)
99
+ addressable (>= 2.8.0)
100
+ crack (>= 0.3.2)
101
+ hashdiff (>= 0.4.0, < 2.0.0)
102
+ zeitwerk (2.6.18)
103
+
104
+ PLATFORMS
105
+ ruby
106
+ x86_64-linux
107
+
108
+ DEPENDENCIES
109
+ dotenv
110
+ lepus!
111
+ pry
112
+ rspec
113
+ rubocop
114
+ rubocop-performance
115
+ rubocop-rspec
116
+ standard
117
+ webmock
118
+
119
+ BUNDLED WITH
120
+ 2.3.22
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Marcos G. Zimmermann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # Lepus
2
+
3
+
4
+ Lepus is a simple and lightweight Ruby library to help you to consume and produce messages to [RabbitMQ](https://www.rabbitmq.com/) using the [Bunny](https://github.com/ruby-amqp/bunny) gem. It's similar to the Sidekiq, Faktory, ActiveJob, SolidQueue, and other libraries, but using RabbitMQ as the message broker.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'lepus'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ ```bash
17
+ bundle install
18
+ ```
19
+
20
+ Or install it yourself as:
21
+
22
+ ```bash
23
+ gem install lepus
24
+ ```
25
+
26
+ ## Usage
27
+
28
+
29
+ ## Configuration
30
+
31
+ You can configure the Lepus using the `Lepus.configure` method. The configuration options are:
32
+
33
+ - `rabbitmq_url`: The RabbitMQ host. Default: to `RABBITMQ_URL` environment variable or `amqp://guest:guest@localhost:5672`.
34
+ - `connection_name`: The connection name. Default: `Lepus`.
35
+ - `recovery_attempts`: The number of attempts to recover the connection. Nil means infinite. Default: `10`.
36
+ - `recover_from_connection_close`: If the connection should be recovered when it's closed. Default: `true`.
37
+ - `app_executor`: The [Rails executor](https://guides.rubyonrails.org/threading_and_code_execution.html#executor) used to wrap asynchronous operations. Only available if you are using Rails. Default: `nil`.
38
+ - `on_thread_error`: The block to be executed when an error occurs on the thread. Default: `nil`.
39
+ - `process_heartbeat_interval`: The interval in seconds between heartbeats. Default is `60 seconds`.
40
+ - `process_alive_threshold`: the threshold in seconds to consider a process alive. Default is `5 minutes`.
41
+
42
+
43
+ ```ruby
44
+ Lepus.configure do |config|
45
+ config.connection_name = 'MyApp'
46
+ config.rabbitmq_url = ENV.fetch('RABBITMQ_URL', 'amqp://guest:guest@localhost:5672')
47
+ end
48
+ ```
49
+
50
+ ## Defining a Consumer
51
+
52
+ To define a consumer, you need to create a class inheriting from `Lepus::Consumer` and implement the `perform` method. The `perform` method will be called when a message is received. Use the `configure` method to set the queue name, exchange name, and other options.
53
+
54
+ The example below defines a consumer with required settings:
55
+
56
+ ```ruby
57
+ class MyConsumer < Lepus::Consumer
58
+ configure(
59
+ queue: "queue_name",
60
+ exchange: "exchange_name",
61
+ routing_key: %w[routing_key1 routing_key2],
62
+ )
63
+
64
+ def perform(message)
65
+ puts "delivery_info: #{message.delivery_info}"
66
+ puts "metadata: #{message.metadata}"
67
+ puts "payload: #{message.payload}"
68
+
69
+ ack!
70
+ end
71
+ end
72
+ ```
73
+
74
+ ### Consumer Configuration
75
+
76
+ The `configure` method accepts the following options:
77
+
78
+ - **\*** `queue`: . The queue name or a Hash with the queue options. Default: `nil`.
79
+ - **\*** `exchange`: The exchange name or a Hash with the exchange options. Default: `nil`.
80
+ - `routing_key`: One or more routing keys. Default: `nil`.
81
+ - `bind`: The binding options. Default: `nil`.
82
+ - `retry_queue`: Boolean or a Hash to configure the retry queue. Default: `false`.
83
+ - `error_queue`: Boolean or a Hash to configure the error queue. Default: `false`.
84
+
85
+ Options marked with `*` are required.
86
+
87
+ You can pass a more descriptive configuration using a Hash with custom options for each part of message broker:
88
+
89
+ ```ruby
90
+ class MyConsumer < Lepus::Consumer
91
+ configure(
92
+ queue: {
93
+ name: "queue_name",
94
+ durable: true,
95
+ # You can set any other exchange options here
96
+ # arguments: { 'x-message-ttl' => 60_000 }
97
+ },
98
+ exchange: {
99
+ name: "exchange_name",
100
+ type: :direct, # You may use :direct, :fanout, :topic, or :headers
101
+ durable: true,
102
+ # You can set any other exchange options here
103
+ # arguments: { 'x-message-ttl' => 60_000 }
104
+ },
105
+ bind: {
106
+ routing_key: %w[routing_key1 routing_key2]
107
+ },
108
+ retry_queue: { # As shortcut, you can just pass `true` to create a retry queue with default options below.
109
+ name: "queue_name.retry",
110
+ durable: true,
111
+ delay: 5000,
112
+ },
113
+ error_queue: { # As shortcut, you can just pass `true` to create an error queue with default options below.
114
+ name: "queue_name.error",
115
+ durable: true,
116
+ }
117
+ )
118
+ # ...
119
+ end
120
+ ```
121
+
122
+ By declaring the `retry_queue` option, it will automatically create a queue named `queue_name.retry` and use the arguments `x-dead-letter-exchange` and `x-dead-letter-routing-key` to route rejected messages to it. When routed to the retry queue, messages will wait there for the number of milliseconds specified in `delay`, after which they will be redelivered to the original queue.
123
+ **Note that this will not automatically catch unhandled errors. You still have to catch any errors yourself and reject your message manually for the retry mechanism to work.**
124
+
125
+ It may result in a infinite loop if the message is always rejected. To avoid this, you can use the `error_queue` option to route the message to an `queue_name.error` queue after a number of attempts by using the `MaxRetry` middleware covered in the next section.
126
+
127
+ Refer to the [Dead Letter Exchanges](https://www.rabbitmq.com/docs/dlx) documentation for more information about retry.
128
+
129
+ ### Middlewares
130
+
131
+ Consumers can use middlewares for recurring tasks like logging, error handling, parsing, and others. It comes with a few built-in middlewares:
132
+
133
+ * `:max_retry`: Rejects the message and routes it to the error queue after a number of attempts.
134
+ * `:json`: Parses the message payload as JSON.
135
+
136
+ You can use the `use` method to add middlewares to the consumer:
137
+
138
+ ```ruby
139
+ class MyConsumer < Lepus::Consumer
140
+ use(
141
+ :max_retry,
142
+ retries: 6,
143
+ error_queue: "queue_name.error" # The queue to route the message after the number of retries
144
+ )
145
+
146
+ use(
147
+ :json,
148
+ symbolize_keys: true,
149
+ on_error: proc { :nack } # The default is :reject on parsing error
150
+ )
151
+
152
+ def perform(message)
153
+ puts message.payload[:name]
154
+ ack!
155
+ end
156
+ end
157
+ ```
158
+
159
+ You can also create your own middlewares, just create subclasses of `Lepus::Middleware` and implement the `call` method:
160
+
161
+ ```ruby
162
+ class MyMiddleware < Lepus::Middleware
163
+ def initialize(**options)
164
+ @options = options
165
+ end
166
+
167
+ def call(message, app
168
+ # Do something before calling the next middleware
169
+ app.call(message)
170
+ end
171
+ end
172
+ ```
173
+
174
+ ## Starting the Consumer Process
175
+
176
+ To start the consumer, can use the `lepus` CLI:
177
+
178
+ ```bash
179
+ bundle exec lepus
180
+ ```
181
+
182
+ You can pass one or more consumers to the `lepus` CLI:
183
+
184
+ ```bash
185
+ bundle exec lepus start MyConsumer1 MyConsumer2 --debug
186
+ ```
187
+
188
+ Each consumer will run in a separate process, and a supervisor will monitor them. If a consumer crashes, the supervisor will restart it.
189
+
190
+ ### Puma Plugin
191
+
192
+ We provide a Puma plugin if you want to run the Lepus's supervisor together with Puma and have Puma monitor and manage it. You just need to add
193
+
194
+ ```ruby
195
+ plugin :lepus
196
+ ```
197
+
198
+ **Note**: The Puma plugin is only available if you are using Puma 6.x or higher.
199
+
200
+ ## Development
201
+
202
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
203
+
204
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
205
+
206
+ ## Contributing
207
+
208
+ Bug reports and pull requests are welcome on GitHub at https://github.com/marcosgz/lepus.
209
+
210
+
211
+ ## License
212
+
213
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "dotenv/load"
6
+ require "pry"
7
+ require "lepus"
8
+
9
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+ find gemfiles -type f \( -iname "*.gemfile" ! -iname "*.lock" \) -exec bundle install --gemfile {} \;
@@ -0,0 +1,8 @@
1
+ services:
2
+ rabbitmq:
3
+ image: rabbitmq
4
+ ports:
5
+ - 5672:5672
6
+ - 15672:15672
7
+ command: >
8
+ sh -c "rabbitmq-plugins enable rabbitmq_management && rabbitmq-server"
data/exec/lepus ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
5
+
6
+ require "lepus"
7
+ require "lepus/cli"
8
+
9
+ Lepus::CLI.start
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: ".."
4
+
5
+ gem "rails", "~> 5.2", ">= 5.2.8.1"