wayfarer 0.4.10 → 0.5.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 +4 -4
- data/.github/actions/setup-mise/action.yaml +22 -0
- data/.github/workflows/push.yaml +89 -0
- data/.github/workflows/release.yaml +2 -2
- data/.gitignore +1 -0
- data/AGENTS.md +265 -0
- data/CLAUDE.md +1 -0
- data/Dockerfile +1 -0
- data/Gemfile.lock +5 -1
- data/docs/cookbook/batch_routing.md +3 -1
- data/docs/cookbook/consent_screen.md +3 -1
- data/docs/cookbook/executing_javascript.md +9 -3
- data/docs/cookbook/navigation.md +9 -3
- data/docs/cookbook/querying_html.md +9 -3
- data/docs/cookbook/screenshots.md +6 -2
- data/docs/guides/cli.md +1 -1
- data/docs/guides/debugging.md +1 -1
- data/docs/guides/handlers.md +1 -1
- data/docs/guides/jobs.md +13 -9
- data/docs/guides/middleware.md +134 -0
- data/docs/guides/navigation.md +6 -3
- data/docs/guides/networking/capybara.md +3 -1
- data/docs/guides/networking/custom_adapters.md +3 -3
- data/docs/guides/networking/ferrum.md +3 -1
- data/docs/guides/networking/http.md +4 -2
- data/docs/guides/networking/selenium.md +7 -3
- data/docs/guides/pages.md +4 -2
- data/docs/guides/routing.md +16 -8
- data/docs/guides/tasks.md +1 -1
- data/docs/guides/tutorial.md +75 -2
- data/docs/guides/user_agents.md +10 -10
- data/docs/index.md +1 -1
- data/lib/wayfarer/base.rb +6 -10
- data/lib/wayfarer/cli.rb +4 -2
- data/lib/wayfarer/gc.rb +0 -1
- data/lib/wayfarer/handler.rb +7 -5
- data/lib/wayfarer/middleware/controller.rb +9 -8
- data/lib/wayfarer/middleware/dependency_graph.rb +181 -0
- data/lib/wayfarer/routing/matchers/query.rb +3 -2
- data/lib/wayfarer.rb +23 -2
- data/mise.toml +2 -0
- data/mkdocs.yml +1 -0
- data/rake/lint.rake +16 -0
- data/spec/wayfarer/cli/routing_spec.rb +36 -6
- data/spec/wayfarer/integration/dependency_graph_spec.rb +111 -0
- data/spec/wayfarer/middleware/controller_spec.rb +34 -9
- data/spec/wayfarer/middleware/dependency_graph_spec.rb +367 -0
- data/spec/wayfarer/routing/integration_spec.rb +18 -0
- data/spec/wayfarer/routing/matchers/query_spec.rb +18 -0
- data/wayfarer.gemspec +1 -1
- metadata +11 -3
- data/.github/workflows/lint.yaml +0 -27
- data/.github/workflows/tests.yaml +0 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e46ffe5b40bd68f97cc1dc1c1777fc18f26ddf25e4b803efa99a6ca5ba8eb96e
|
|
4
|
+
data.tar.gz: 45359bcb3a30ac6d80052e05ecc35a78321c1b5d58fda6f6f2bf263375c9661d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 510c6839238f636cea908e27baed38b5f21999c56ef60486e1d8f23e006a205a25cd4e01f8ef017c52f35f91ae96fc198e582f8b517353128911cc7d355d76f0
|
|
7
|
+
data.tar.gz: a34c0c7a57688c4fc6442261391dcd8f55509711dbab4bd223303c376a8ca2a8f9e5e618cab472ef041c6cd3f1a660834a7d301b3d41624ea7cb79a42f19fa2e
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Setup mise
|
|
2
|
+
description: Installs mise and tools
|
|
3
|
+
runs:
|
|
4
|
+
using: composite
|
|
5
|
+
steps:
|
|
6
|
+
- name: Install mise
|
|
7
|
+
shell: bash
|
|
8
|
+
run: |
|
|
9
|
+
sudo apt update -y && sudo apt install -y curl
|
|
10
|
+
sudo install -dm 755 /etc/apt/keyrings
|
|
11
|
+
curl -fSs https://mise.jdx.dev/gpg-key.pub | sudo tee /etc/apt/keyrings/mise-archive-keyring.asc 1> /dev/null
|
|
12
|
+
echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.asc] https://mise.jdx.dev/deb stable main" | sudo tee /etc/apt/sources.list.d/mise.list
|
|
13
|
+
sudo apt update -y
|
|
14
|
+
sudo apt install -y mise
|
|
15
|
+
|
|
16
|
+
- name: Add mise shims to PATH
|
|
17
|
+
shell: bash
|
|
18
|
+
run: echo "$HOME/.local/share/mise/shims" >> $GITHUB_PATH
|
|
19
|
+
|
|
20
|
+
- name: Install tools
|
|
21
|
+
shell: bash
|
|
22
|
+
run: mise install -y
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
name: Push
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- '*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: "pages"
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
lint:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Start services
|
|
24
|
+
run: docker compose up -d wayfarer
|
|
25
|
+
|
|
26
|
+
- name: RuboCop
|
|
27
|
+
run: docker compose run --rm --name test wayfarer bundle exec rake lint:rubocop
|
|
28
|
+
|
|
29
|
+
- name: Verify RubyGem platforms
|
|
30
|
+
if: success() || failure()
|
|
31
|
+
run: docker compose run --rm wayfarer bundle exec rake lint:platforms
|
|
32
|
+
|
|
33
|
+
- name: Setup mise
|
|
34
|
+
if: success() || failure()
|
|
35
|
+
uses: ./.github/actions/setup-mise
|
|
36
|
+
|
|
37
|
+
- name: Vale
|
|
38
|
+
if: success() || failure()
|
|
39
|
+
env:
|
|
40
|
+
VALE_STYLES_PATH: /tmp/value
|
|
41
|
+
run: |
|
|
42
|
+
vale sync
|
|
43
|
+
vale --glob="*.md" $(git diff --name-only origin/${{ github.base_ref }}...HEAD)
|
|
44
|
+
|
|
45
|
+
unit-tests:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- name: Set up Docker Compose
|
|
51
|
+
run: docker compose up -d
|
|
52
|
+
|
|
53
|
+
- name: Run tests
|
|
54
|
+
run: docker compose exec -T -e CI=$CI wayfarer bundle exec rake test
|
|
55
|
+
|
|
56
|
+
- name: Verify gem version
|
|
57
|
+
run: docker compose exec -T -e CI=true wayfarer bundle exec rake release:guard_versions
|
|
58
|
+
|
|
59
|
+
check-links:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v4
|
|
63
|
+
|
|
64
|
+
- name: Setup mise
|
|
65
|
+
uses: ./.github/actions/setup-mise
|
|
66
|
+
|
|
67
|
+
- name: Install dependencies
|
|
68
|
+
run: pip install -r requirements.txt
|
|
69
|
+
|
|
70
|
+
- name: Build site (Strict)
|
|
71
|
+
run: python -m mkdocs build --strict
|
|
72
|
+
|
|
73
|
+
- name: Upload artifact
|
|
74
|
+
if: github.ref_name == github.event.repository.default_branch
|
|
75
|
+
uses: actions/upload-pages-artifact@v3
|
|
76
|
+
with:
|
|
77
|
+
path: 'site'
|
|
78
|
+
|
|
79
|
+
deploy-docs:
|
|
80
|
+
needs: check-links
|
|
81
|
+
if: github.ref_name == github.event.repository.default_branch
|
|
82
|
+
environment:
|
|
83
|
+
name: github-pages
|
|
84
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- name: Deploy to GitHub Pages
|
|
88
|
+
id: deployment
|
|
89
|
+
uses: actions/deploy-pages@v4
|
|
@@ -4,10 +4,10 @@ on: workflow_dispatch
|
|
|
4
4
|
|
|
5
5
|
jobs:
|
|
6
6
|
release:
|
|
7
|
-
if: github.ref_name == 'master'
|
|
7
|
+
if: github.ref_name == 'master'
|
|
8
8
|
runs-on: ubuntu-latest
|
|
9
9
|
steps:
|
|
10
|
-
- uses: actions/checkout@
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
11
|
|
|
12
12
|
- run: docker compose up -d
|
|
13
13
|
|
data/.gitignore
CHANGED
data/AGENTS.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Wayfarer -- AI Assistant Guide
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
Wayfarer is a Ruby gem (v0.5.0) for versatile web crawling, built on top of **ActiveJob**. It provides a middleware-based pipeline for fetching, parsing, deduplicating, and routing web pages, with support for plain HTTP, headless Chrome (Ferrum/Capybara), and Selenium browser automation.
|
|
6
|
+
|
|
7
|
+
- **Gem name:** `wayfarer`
|
|
8
|
+
- **Ruby version:** 3.4.4 (enforced via `mise.toml`)
|
|
9
|
+
- **License:** MIT
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Technology Stack
|
|
14
|
+
|
|
15
|
+
| Layer | Technology |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Language | Ruby 3.4.4 |
|
|
18
|
+
| Job framework | ActiveJob 7.1+ |
|
|
19
|
+
| Deduplication/state | Redis 5.4+ |
|
|
20
|
+
| Plain HTTP | net-http-persistent 4.0+ |
|
|
21
|
+
| Headless Chrome (CDP) | Ferrum 0.17+ |
|
|
22
|
+
| Browser (Selenium) | selenium-webdriver 4.35+ |
|
|
23
|
+
| Browser wrapper | Capybara 3.4+ |
|
|
24
|
+
| HTML/XML parsing | Nokogiri 1.18+ |
|
|
25
|
+
| Meta-tag parsing | MetaInspector 5.16+ |
|
|
26
|
+
| URL routing/matching | Mustermann 3.0+ |
|
|
27
|
+
| URL normalization | Addressable 2.8+ |
|
|
28
|
+
| Autoloading | Zeitwerk 2.7+ |
|
|
29
|
+
| CLI | Thor 1.0+ |
|
|
30
|
+
| Connection pooling | connection_pool 2.5+ |
|
|
31
|
+
| Testing | RSpec 3.13+ |
|
|
32
|
+
| Linting | RuboCop 1.80+ |
|
|
33
|
+
| Documentation | MkDocs (Material theme) + YARD |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Development Workflow
|
|
38
|
+
|
|
39
|
+
### Initial Setup
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
mise install # Install Ruby 3.4.4, Python 3.13, vale
|
|
43
|
+
bundle install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Docker (recommended for CI-equivalent runs)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
docker compose up -d
|
|
50
|
+
docker compose exec -T -e CI=true wayfarer bundle exec rake test
|
|
51
|
+
docker compose run --rm wayfarer bundle exec rake lint:rubocop
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Interactive Console
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle exec rake console # Pry REPL with wayfarer loaded
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Running Tests
|
|
63
|
+
|
|
64
|
+
Tests use **RSpec** with tag-based filtering. Always run via Rake, not `rspec` directly.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bundle exec rake test # All tests
|
|
68
|
+
bundle exec rake test:isolated # No browser/Redis -- fastest
|
|
69
|
+
bundle exec rake test:integration # Redis + CLI
|
|
70
|
+
bundle exec rake test:selenium # Selenium browser
|
|
71
|
+
bundle exec rake test:ferrum # Ferrum (Chrome CDP)
|
|
72
|
+
bundle exec rake test:cli # CLI tests
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**RSpec tags:** `:selenium`, `:ferrum`, `:cli`, `:redis`. Untagged tests run in `test:isolated`.
|
|
76
|
+
|
|
77
|
+
A Sinatra test server starts automatically on port 9876 before the suite. FactoryBot factories are in `spec/factories/`. The shared context `"with Redis"` is auto-included for `:redis`-tagged examples.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Linting
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
bundle exec rake lint:rubocop
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Key RuboCop settings: 120-char line limit, double-quoted strings, outdented access modifiers, max 20-line methods, AbcSize <= 30, RSpec max 5 expectations/example and max 5 nested groups.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Common Ruby Patterns
|
|
92
|
+
|
|
93
|
+
### ActiveSupport::Concern for Mixins
|
|
94
|
+
|
|
95
|
+
The codebase uses `extend ActiveSupport::Concern` throughout to structure reusable modules with `included` and `class_methods` blocks. The `included` block runs on inclusion; `class_methods` defines class-level behavior without verbose `def self.included` boilerplate.
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
module Base
|
|
99
|
+
extend ActiveSupport::Concern
|
|
100
|
+
|
|
101
|
+
included do
|
|
102
|
+
include Wayfarer::Middleware::Controller
|
|
103
|
+
use Wayfarer::Middleware::Redis
|
|
104
|
+
use Wayfarer::Middleware::UriParser
|
|
105
|
+
# ...
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class_methods do
|
|
109
|
+
def crawl(url, batch: SecureRandom.uuid)
|
|
110
|
+
Task.new(url, batch).tap { |task| perform_later(task) }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Middleware Chain via Recursive Lambda
|
|
117
|
+
|
|
118
|
+
`Middleware::Chain` composes middleware as a recursive lambda. Each middleware receives a block representing the rest of the chain, and calls `yield` to continue or returns early to halt.
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
def call(task)
|
|
122
|
+
traverse = lambda do |i|
|
|
123
|
+
if middlewares[i]
|
|
124
|
+
middlewares[i].call(task) { traverse.call(i + 1) }
|
|
125
|
+
elsif block_given?
|
|
126
|
+
yield
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
traverse.call(0)
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Strategy Pattern via Module Inclusion
|
|
134
|
+
|
|
135
|
+
`Networking::Strategy` defines the abstract interface (raising `NotImplementedError`) and shared helpers (`success`, `redirect`). Concrete agents (`HTTP`, `Ferrum`, `Selenium`, `Capybara`) include the mixin and override the required methods.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
module Strategy
|
|
139
|
+
extend ActiveSupport::Concern
|
|
140
|
+
|
|
141
|
+
def fetch(instance, url) = navigate(instance, url).then { live(instance) }
|
|
142
|
+
def navigate(_instance, _url) = raise NotImplementedError
|
|
143
|
+
def create = raise NotImplementedError
|
|
144
|
+
def destroy(_instance); end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
def success(...) = Networking::Result::Success.new(Page.new(...))
|
|
148
|
+
def redirect(...) = Networking::Result::Redirect.new(...)
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Fluent Routing DSL
|
|
153
|
+
|
|
154
|
+
`Routing::DSL` provides chainable builder methods. Each method calls `child_route`, which creates a child `Route` and appends it to `children`. Inline keyword arguments and blocks are both supported, enabling two equivalent syntaxes:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
# Inline
|
|
158
|
+
route.host("example.com", path: "/articles/:id", to: :article)
|
|
159
|
+
|
|
160
|
+
# Block
|
|
161
|
+
route.host("example.com") do
|
|
162
|
+
path "/articles/:id", to: :article
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The method signature uses `**` and `&` forwarding to keep DSL methods concise. Last `.to()` call wins when routes conflict.
|
|
167
|
+
|
|
168
|
+
### Visitor Pattern for Tree Traversal
|
|
169
|
+
|
|
170
|
+
Routes form a tree searched depth-first by `Routing::PathFinder`. Routes call `accept(visitor)`, which calls `enter`, `visit`, and `leave` on the visitor. This separates traversal logic from matching logic while allowing the visitor to accumulate mutable state (params, matched actions).
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
# In Route
|
|
174
|
+
def accept(visitor)
|
|
175
|
+
visitor.enter(self)
|
|
176
|
+
return visitor.leave unless visitor.visit(self)
|
|
177
|
+
children.each { |child| child.accept(visitor) }
|
|
178
|
+
visitor.leave
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Dynamic Method Generation via Module Subclasses
|
|
183
|
+
|
|
184
|
+
`Logging.emit` returns an anonymous `Module` subclass whose `included` hook uses `define_method` to inject a `log` method. This lets each middleware declare its log messages declaratively at the class level.
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
class Router
|
|
188
|
+
include Wayfarer::Logging.emit(
|
|
189
|
+
mismatch: [:info, "No matching route"],
|
|
190
|
+
match: [:info, "Routing to %<action>s"],
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Singleton + Connection Pooling
|
|
196
|
+
|
|
197
|
+
`Networking::Pool` is a `Singleton` wrapping `ConnectionPool`. Cleanup is registered via `at_exit`. A `class_attribute :finalizer` lambda allows customizing shutdown behavior without subclassing. `ConnectionPool::TimeoutError` is wrapped into the gem's own error class at the boundary.
|
|
198
|
+
|
|
199
|
+
### Configuration Syntax
|
|
200
|
+
|
|
201
|
+
Use bracket syntax `Wayfarer.config[:key][:subkey]` instead of `dig` for predictable configuration access.
|
|
202
|
+
|
|
203
|
+
### Value Objects with Declarative Stringify
|
|
204
|
+
|
|
205
|
+
Value objects (`Task`, `Page`) include lightweight mixins like `Stringify` and `KV`. `stringify :url, :batch` registers which attributes appear in `#to_s`/`#inspect`. Custom `ActiveJob::Serializers::ObjectSerializer` subclasses handle serialization to/from JSON.
|
|
206
|
+
|
|
207
|
+
### Inline Error Classes
|
|
208
|
+
|
|
209
|
+
Lightweight custom errors are defined inline at module scope:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
UserAgentTimeoutError = Class.new(StandardError)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Rescue lists are built dynamically from configuration:
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
rescue *strategy.class.renew_on, *Wayfarer.config[:network][:renew_on] => e
|
|
219
|
+
renew
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Documentation
|
|
226
|
+
|
|
227
|
+
### Writing Style
|
|
228
|
+
|
|
229
|
+
Documentation in `docs/` must strictly mimic the voice, tone, and typography of existing guides. Key rules:
|
|
230
|
+
|
|
231
|
+
- **Address the reader directly** using second person ("you can", "use the")
|
|
232
|
+
- **Use contractions** ("isn't", "doesn't", "it's") -- Vale enforces `Google.Contractions`
|
|
233
|
+
- **Prefer active voice** over passive voice ("the graph compiles" not "the chain is compiled")
|
|
234
|
+
- **Use ASCII characters** -- write `word--word` not Unicode em dashes. Avoid Unicode dashes, quotes, and other non-ASCII punctuation unless explicitly required.
|
|
235
|
+
- **Headings use sentence-style capitalization** -- `## Adding middleware` not `## Adding Middleware`
|
|
236
|
+
- **Keep paragraphs short** -- one concept per paragraph, 2-3 sentences max
|
|
237
|
+
- **Code references use backticks** -- `` `use` ``, `` `dependency_graph.remove` ``
|
|
238
|
+
- **Cross-link related guides** -- `[tasks](tasks.md)`, `[handlers](handlers.md)`
|
|
239
|
+
- **Use mkdocs-material admonitions** for callouts -- `!!! note`, `!!! warning`, `!!! danger`
|
|
240
|
+
|
|
241
|
+
### Linting with Vale
|
|
242
|
+
|
|
243
|
+
Documentation is linted with [Vale](https://vale.sh) using the Google style guide. Configuration lives in `.vale.ini` and domain vocabulary in `styles/config/vocabularies/Wayfarer/accept.txt`.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
vale sync # Download style packages (run once)
|
|
247
|
+
vale docs/guides/middleware.md # Lint a specific file
|
|
248
|
+
vale docs/ # Lint all documentation
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Fix all errors and warnings. Suggestions (passive voice, acronym expansion) are acceptable when they match the existing documentation style.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Key Conventions
|
|
256
|
+
|
|
257
|
+
- **All files:** `# frozen_string_literal: true` at the top
|
|
258
|
+
- **Strings:** Always double-quoted
|
|
259
|
+
- **Modules vs Classes:** Namespaced under `Wayfarer::*`; use `ActiveSupport::Concern` for mixins
|
|
260
|
+
- **Autoloading:** Zeitwerk with custom inflections -- file names must match constant names after inflection (`cli` -> `CLI`, `dsl` -> `DSL`, `http` -> `HTTP`, `uri` -> `URI`, `url` -> `URL`, etc.)
|
|
261
|
+
- **Numeric comparisons:** Use `== 0` not `.zero?`
|
|
262
|
+
- **No module documentation cop** -- use YARD `@param`/`@return`/`@yield`/`@example`/`@see` tags to document complex logic, public APIs, and overridden template methods instead. Use comments to explain *why* something is done.
|
|
263
|
+
- **Specs:** Tag browser tests (`:selenium`, `:ferrum`), Redis tests (`:redis`), CLI tests (`:cli`); all others run isolated
|
|
264
|
+
- **Do not skip RuboCop** without an inline disable comment with justification
|
|
265
|
+
- **Version consistency:** `VERSION::STRING` in `lib/wayfarer.rb` must match `wayfarer.gemspec`; CI enforces via `rake release:guard_versions`
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
data/Dockerfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
wayfarer (0.
|
|
4
|
+
wayfarer (0.5.0)
|
|
5
5
|
activejob (>= 7.1)
|
|
6
6
|
addressable (~> 2.8)
|
|
7
7
|
capybara (~> 3.4)
|
|
@@ -98,6 +98,7 @@ GEM
|
|
|
98
98
|
concurrent-ruby (~> 1.1)
|
|
99
99
|
webrick (~> 1.7)
|
|
100
100
|
websocket-driver (~> 0.7)
|
|
101
|
+
ffi (1.17.2-arm64-darwin)
|
|
101
102
|
ffi (1.17.2-x86_64-darwin)
|
|
102
103
|
ffi (1.17.2-x86_64-linux-musl)
|
|
103
104
|
globalid (1.2.1)
|
|
@@ -148,6 +149,8 @@ GEM
|
|
|
148
149
|
net-http-persistent (4.0.6)
|
|
149
150
|
connection_pool (~> 2.2, >= 2.2.4)
|
|
150
151
|
nio4r (2.7.4)
|
|
152
|
+
nokogiri (1.18.9-arm64-darwin)
|
|
153
|
+
racc (~> 1.4)
|
|
151
154
|
nokogiri (1.18.9-x86_64-darwin)
|
|
152
155
|
racc (~> 1.4)
|
|
153
156
|
nokogiri (1.18.9-x86_64-linux-musl)
|
|
@@ -302,6 +305,7 @@ GEM
|
|
|
302
305
|
zlib (3.2.1)
|
|
303
306
|
|
|
304
307
|
PLATFORMS
|
|
308
|
+
arm64-darwin-25
|
|
305
309
|
x86_64-darwin-24
|
|
306
310
|
x86_64-linux-musl
|
|
307
311
|
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
# Create a record in an external database and store the hostname
|
|
5
5
|
record = Database::Row.create(hostname: "example.com")
|
|
6
6
|
|
|
7
|
-
class DummyJob <
|
|
7
|
+
class DummyJob < ActiveJob::Base
|
|
8
|
+
include Wayfarer::Base
|
|
9
|
+
|
|
8
10
|
route do |hostname|
|
|
9
11
|
host hostname, to: :index
|
|
10
12
|
end
|
|
@@ -8,7 +8,9 @@ iframe, clicked, and makes the live page behind the screen accessible to
|
|
|
8
8
|
```ruby
|
|
9
9
|
Wayfarer.config[:network][:agent] = :ferrum
|
|
10
10
|
|
|
11
|
-
class DummyJob <
|
|
11
|
+
class DummyJob < ActiveJob::Base
|
|
12
|
+
include Wayfarer::Base
|
|
13
|
+
|
|
12
14
|
route.to :index, host: "example.com"
|
|
13
15
|
|
|
14
16
|
before_action if: :consent_required? do
|
|
@@ -5,7 +5,9 @@ Executing JavaScript requires automating a browser.
|
|
|
5
5
|
=== "Ferrum"
|
|
6
6
|
|
|
7
7
|
```ruby
|
|
8
|
-
class DummyJob <
|
|
8
|
+
class DummyJob < ActiveJob::Base
|
|
9
|
+
include Wayfarer::Base
|
|
10
|
+
|
|
9
11
|
route.to :index
|
|
10
12
|
|
|
11
13
|
def index
|
|
@@ -17,7 +19,9 @@ Executing JavaScript requires automating a browser.
|
|
|
17
19
|
=== "Selenium"
|
|
18
20
|
|
|
19
21
|
```ruby
|
|
20
|
-
class DummyJob <
|
|
22
|
+
class DummyJob < ActiveJob::Base
|
|
23
|
+
include Wayfarer::Base
|
|
24
|
+
|
|
21
25
|
route.to :index
|
|
22
26
|
|
|
23
27
|
def index
|
|
@@ -30,7 +34,9 @@ Executing JavaScript requires automating a browser.
|
|
|
30
34
|
=== "Capybara"
|
|
31
35
|
|
|
32
36
|
```ruby
|
|
33
|
-
class DummyJob <
|
|
37
|
+
class DummyJob < ActiveJob::Base
|
|
38
|
+
include Wayfarer::Base
|
|
39
|
+
|
|
34
40
|
route.to :index
|
|
35
41
|
|
|
36
42
|
def index
|
data/docs/cookbook/navigation.md
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
=== "Ferrum"
|
|
4
4
|
|
|
5
5
|
```ruby
|
|
6
|
-
class DummyJob <
|
|
6
|
+
class DummyJob < ActiveJob::Base
|
|
7
|
+
include Wayfarer::Base
|
|
8
|
+
|
|
7
9
|
route.to :index
|
|
8
10
|
|
|
9
11
|
def index
|
|
@@ -17,7 +19,9 @@
|
|
|
17
19
|
=== "Selenium"
|
|
18
20
|
|
|
19
21
|
```ruby
|
|
20
|
-
class DummyJob <
|
|
22
|
+
class DummyJob < ActiveJob::Base
|
|
23
|
+
include Wayfarer::Base
|
|
24
|
+
|
|
21
25
|
route.to :index
|
|
22
26
|
|
|
23
27
|
def index
|
|
@@ -31,7 +35,9 @@
|
|
|
31
35
|
=== "Capybara"
|
|
32
36
|
|
|
33
37
|
```ruby
|
|
34
|
-
class DummyJob <
|
|
38
|
+
class DummyJob < ActiveJob::Base
|
|
39
|
+
include Wayfarer::Base
|
|
40
|
+
|
|
35
41
|
route.to :index
|
|
36
42
|
|
|
37
43
|
def index
|
|
@@ -5,7 +5,9 @@ See: [Nokogiri: Searching an HTML / XML Document](https://nokogiri.org/tutorials
|
|
|
5
5
|
=== "Nokogiri"
|
|
6
6
|
|
|
7
7
|
```ruby
|
|
8
|
-
class DummyJob <
|
|
8
|
+
class DummyJob < ActiveJob::Base
|
|
9
|
+
include Wayfarer::Base
|
|
10
|
+
|
|
9
11
|
route.to :index
|
|
10
12
|
|
|
11
13
|
def index
|
|
@@ -18,7 +20,9 @@ See: [Nokogiri: Searching an HTML / XML Document](https://nokogiri.org/tutorials
|
|
|
18
20
|
=== "Ferrum"
|
|
19
21
|
|
|
20
22
|
```ruby
|
|
21
|
-
class DummyJob <
|
|
23
|
+
class DummyJob < ActiveJob::Base
|
|
24
|
+
include Wayfarer::Base
|
|
25
|
+
|
|
22
26
|
route.to :index
|
|
23
27
|
|
|
24
28
|
def index
|
|
@@ -31,7 +35,9 @@ See: [Nokogiri: Searching an HTML / XML Document](https://nokogiri.org/tutorials
|
|
|
31
35
|
=== "Selenium"
|
|
32
36
|
|
|
33
37
|
```ruby
|
|
34
|
-
class DummyJob <
|
|
38
|
+
class DummyJob < ActiveJob::Base
|
|
39
|
+
include Wayfarer::Base
|
|
40
|
+
|
|
35
41
|
route.to :index
|
|
36
42
|
|
|
37
43
|
def index
|
|
@@ -5,7 +5,9 @@ Taking screenshots requires automating a browser.
|
|
|
5
5
|
=== "Ferrum"
|
|
6
6
|
|
|
7
7
|
```ruby
|
|
8
|
-
class DummyJob <
|
|
8
|
+
class DummyJob < ActiveJob::Base
|
|
9
|
+
include Wayfarer::Base
|
|
10
|
+
|
|
9
11
|
route.to :index
|
|
10
12
|
|
|
11
13
|
def index
|
|
@@ -17,7 +19,9 @@ Taking screenshots requires automating a browser.
|
|
|
17
19
|
=== "Selenium"
|
|
18
20
|
|
|
19
21
|
```ruby
|
|
20
|
-
class DummyJob <
|
|
22
|
+
class DummyJob < ActiveJob::Base
|
|
23
|
+
include Wayfarer::Base
|
|
24
|
+
|
|
21
25
|
route.to :index
|
|
22
26
|
|
|
23
27
|
def index
|
data/docs/guides/cli.md
CHANGED
|
@@ -8,7 +8,7 @@ The command-line interface to Wayfarer.
|
|
|
8
8
|
wayfarer [OPTIONS] [perform|enqueue|execute|route|tree]
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
See [Configuration](
|
|
11
|
+
See [Configuration](configuration.md) for the respected environment variables.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
data/docs/guides/debugging.md
CHANGED
data/docs/guides/handlers.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Handlers
|
|
2
2
|
|
|
3
|
-
Handlers are like [jobs](
|
|
3
|
+
Handlers are like [jobs](jobs.md) but they don't inherit from `ActiveJob::Base`
|
|
4
4
|
which is why they can't affect the message queue directly themselves.
|
|
5
5
|
Instead, jobs and handlers can route tasks to other handlers. Handlers
|
|
6
6
|
themselves have routes, but they can be bypassed.
|