jekyll-meilisearch 0.3.0 → 0.4.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/README.md +31 -51
- data/lib/jekyll-meilisearch/generator.rb +55 -56
- data/lib/jekyll-meilisearch/version.rb +1 -1
- data/lib/jekyll-meilisearch.rb +4 -4
- metadata +63 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b88f29e6292e57d37582a005ccd165d9916bead6896ec325effc4f7122a841dc
|
4
|
+
data.tar.gz: 44cc18269f75390ebb9849c2a6eee43f8441a1046b90379697171a10e8cb070b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc89972661de0f3c9163fd9086bc60b8a28500745514f15f5155ef65265d4c7aeb6210d3fb64d62e58730c7d9a4b080b2a7b145717119a59746f86429330a80e
|
7
|
+
data.tar.gz: c3f56c3d650ab6038247eba09c25896e21e70c387d459a6a6fb2e5373469fe936a7c917996406c14379bb8ab16aa7f560e76870bea0459b5ff30aa0aa4a057a3
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Jekyll Meilisearch Plugin
|
2
|
+
|
2
3
|
A Jekyll plugin that indexes your site’s content into Meilisearch, a fast and lightweight search engine. This plugin supports incremental indexing, ensuring efficient updates by only syncing changes between your Jekyll site and Meilisearch.
|
3
4
|
|
5
|
+
[](https://github.com/unicolored/jekyll-meilisearch/actions/workflows/ruby.yml) [](https://badge.fury.io/rb/jekyll-meilisearch)
|
6
|
+
|
4
7
|
## Features
|
5
8
|
- Indexes Jekyll collections (e.g., posts, pages) into Meilisearch.
|
6
9
|
- Incremental updates: adds new documents, deletes obsolete ones, and skips unchanged content.
|
@@ -9,22 +12,18 @@ A Jekyll plugin that indexes your site’s content into Meilisearch, a fast and
|
|
9
12
|
- Pagination support for large sites.
|
10
13
|
|
11
14
|
## Installation
|
12
|
-
Add the gem to your Jekyll site’s Gemfile:
|
13
|
-
|
14
|
-
```shell
|
15
|
-
gem "jekyll-meilisearch", "~> 0.2.0"
|
16
|
-
```
|
17
15
|
|
18
|
-
|
16
|
+
Add the gem to your Jekyll site’s Gemfile:
|
19
17
|
|
20
|
-
```
|
21
|
-
|
18
|
+
```ruby
|
19
|
+
gem "jekyll-meilisearch"
|
22
20
|
```
|
23
21
|
|
24
|
-
|
22
|
+
And then add this line to your site's `_config.yml`:
|
25
23
|
|
26
|
-
```
|
27
|
-
|
24
|
+
```yml
|
25
|
+
plugins:
|
26
|
+
- jekyll-meilisearch
|
28
27
|
```
|
29
28
|
|
30
29
|
## Configuration
|
@@ -44,15 +43,15 @@ meilisearch:
|
|
44
43
|
```
|
45
44
|
|
46
45
|
## Configuration Options
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
46
|
+
* `url`: The Meilisearch server URL (required).
|
47
|
+
* `api_key`: The Meilisearch API key (required). Recommended: use a dedicated api key for your index, not the admin one.
|
48
|
+
* `index_name`: The name of the Meilisearch index (optional, defaults to jekyll_documents).
|
49
|
+
* `collections`: A hash of Jekyll collections to index.
|
50
|
+
* `fields`: Array of fields to extract from each document (e.g., title, content, url, date).
|
51
|
+
* `id_format`: How to generate document IDs:
|
52
|
+
* "default" | "id": Uses collection-name-number if a number field exists, otherwise sanitizes the document ID.
|
53
|
+
* "url": Uses the document’s URL, sanitized.
|
54
|
+
* fallback: if "number" exists, uses "collection_name" + "number"
|
56
55
|
|
57
56
|
Run your Jekyll build:
|
58
57
|
|
@@ -115,39 +114,20 @@ Include the following for adding search to your front :
|
|
115
114
|
|
116
115
|
```
|
117
116
|
|
118
|
-
##
|
119
|
-
- Ruby >= 2.7
|
120
|
-
- Jekyll >= 3.0, < 5.0
|
121
|
-
- Meilisearch server (local or hosted)
|
122
|
-
|
123
|
-
## Dependencies:
|
124
|
-
- httparty (for HTTP requests)
|
125
|
-
|
126
|
-
These are automatically installed when you add the gem to your Gemfile.
|
127
|
-
|
128
|
-
## Development
|
129
|
-
To contribute or modify the plugin:
|
117
|
+
## Skip development
|
130
118
|
|
131
|
-
|
132
|
-
|
133
|
-
- Make changes and test locally: gem build jekyll-meilisearch.gemspec gem install ./jekyll-meilisearch-0.1.0.gem
|
119
|
+
Use `disable_in_development: true` if you want to turn off meilisearch indexation when `jekyll.environment == "development"`,
|
120
|
+
but don't want to remove the plugin (so you don't accidentally commit the removal). Default value is `false`.
|
134
121
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
## License
|
141
|
-
This project is licensed under the MIT License.
|
122
|
+
```yml
|
123
|
+
meilisearch:
|
124
|
+
disable_in_development: true
|
125
|
+
```
|
142
126
|
|
143
127
|
## Contributing
|
144
|
-
Feel free to open issues or submit pull requests on GitHub.
|
145
128
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
# Push the gem
|
152
|
-
gem push jekyll-meilisearch-${version}.gem
|
153
|
-
```
|
129
|
+
1. Fork it (https://github.com/unicolored/jekyll-meilisearch/fork)
|
130
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
131
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
132
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
133
|
+
5. Create a new Pull Request
|
@@ -12,7 +12,7 @@ module JekyllMeilisearch
|
|
12
12
|
Jekyll.logger.info "Jekyll Meilisearch:", "Skipping meilisearch indexation in development"
|
13
13
|
return
|
14
14
|
end
|
15
|
-
Jekyll.logger.info
|
15
|
+
Jekyll.logger.info "Starting Meilisearch incremental indexing..."
|
16
16
|
return unless validate_config
|
17
17
|
|
18
18
|
@documents = build_documents
|
@@ -27,12 +27,12 @@ module JekyllMeilisearch
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def validate_config
|
30
|
-
unless config[
|
31
|
-
Jekyll.logger.info
|
30
|
+
unless config["url"]
|
31
|
+
Jekyll.logger.info "Error: Meilisearch URL not set in config. Skipping indexing."
|
32
32
|
return false
|
33
33
|
end
|
34
|
-
unless config[
|
35
|
-
Jekyll.logger.info
|
34
|
+
unless config["api_key"]
|
35
|
+
Jekyll.logger.info "Error: Meilisearch API key not set in config. Skipping indexing."
|
36
36
|
return false
|
37
37
|
end
|
38
38
|
true
|
@@ -40,34 +40,34 @@ module JekyllMeilisearch
|
|
40
40
|
|
41
41
|
def build_headers(api_key)
|
42
42
|
{
|
43
|
-
|
44
|
-
|
43
|
+
"Content-Type" => "application/json",
|
44
|
+
"Authorization" => "Bearer #{api_key}",
|
45
45
|
}
|
46
46
|
end
|
47
47
|
|
48
48
|
def build_documents
|
49
49
|
documents = []
|
50
|
-
collections_config = config[
|
50
|
+
collections_config = config["collections"] || { "posts" => { "fields" => %w(title content url date) } }
|
51
51
|
|
52
52
|
collections_config.each do |collection_name, collection_settings|
|
53
53
|
collection = @site.collections[collection_name]
|
54
54
|
if collection
|
55
55
|
Jekyll.logger.info "Processing collection: '#{collection_name}'..."
|
56
|
-
fields_to_index = collection_settings[
|
57
|
-
id_format = collection_settings[
|
56
|
+
fields_to_index = collection_settings["fields"] || %w(title content url date)
|
57
|
+
id_format = collection_settings["id_format"] || :default
|
58
58
|
|
59
59
|
collection_docs = collection.docs.map do |doc|
|
60
60
|
sanitized_id = generate_id(doc, collection_name, id_format)
|
61
61
|
doc_data = {
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
"id" => sanitized_id,
|
63
|
+
"content" => doc.content.strip,
|
64
|
+
"url" => doc.url,
|
65
65
|
}
|
66
66
|
fields_to_index.each do |field|
|
67
|
-
next if %w
|
67
|
+
next if %w(id content url).include?(field)
|
68
68
|
|
69
69
|
value = doc.data[field]
|
70
|
-
doc_data[field] = field ==
|
70
|
+
doc_data[field] = field == "date" && value ? value.strftime("%Y-%m-%d") : value
|
71
71
|
end
|
72
72
|
doc_data
|
73
73
|
end
|
@@ -78,7 +78,7 @@ module JekyllMeilisearch
|
|
78
78
|
end
|
79
79
|
|
80
80
|
if documents.empty?
|
81
|
-
Jekyll.logger.info "No documents found across configured collections: #{collections_config.keys.join(
|
81
|
+
Jekyll.logger.info "No documents found across configured collections: #{collections_config.keys.join(", ")}. Cleaning up index..."
|
82
82
|
end
|
83
83
|
documents
|
84
84
|
end
|
@@ -86,11 +86,10 @@ module JekyllMeilisearch
|
|
86
86
|
def generate_id(doc, collection_name, id_format)
|
87
87
|
# Helper method to normalize strings
|
88
88
|
normalize = lambda do |str|
|
89
|
-
str.
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
.slice(0, 100)
|
89
|
+
str.tr("/", "-")
|
90
|
+
.squeeze(%r![^a-zA-Z0-9_-]!, "-").squeeze("-")
|
91
|
+
.downcase
|
92
|
+
.slice(0, 100)
|
94
93
|
end
|
95
94
|
|
96
95
|
case id_format
|
@@ -99,26 +98,26 @@ module JekyllMeilisearch
|
|
99
98
|
when :url
|
100
99
|
normalize.call(doc.url)
|
101
100
|
else
|
102
|
-
doc.data[
|
101
|
+
doc.data["number"] ? "#{collection_name}-#{doc.data["number"]}" : normalize.call(doc.id)
|
103
102
|
end
|
104
103
|
end
|
105
104
|
|
106
105
|
def sync_with_meilisearch
|
107
|
-
headers = build_headers(config[
|
108
|
-
index_name = config[
|
109
|
-
create_index_if_missing(config[
|
106
|
+
headers = build_headers(config["api_key"])
|
107
|
+
index_name = config["index_name"] || "jekyll_documents"
|
108
|
+
create_index_if_missing(config["url"], index_name, headers)
|
110
109
|
|
111
|
-
meili_docs = fetch_all_documents(config[
|
110
|
+
meili_docs = fetch_all_documents(config["url"], index_name, headers)
|
112
111
|
if meili_docs.nil?
|
113
|
-
Jekyll.logger.info
|
114
|
-
return full_index(config[
|
112
|
+
Jekyll.logger.info "Failed to fetch existing documents. Falling back to full indexing."
|
113
|
+
return full_index(config["url"], index_name, @documents, headers)
|
115
114
|
end
|
116
115
|
|
117
|
-
meili_ids = meili_docs.map { |doc| doc[
|
118
|
-
jekyll_ids = @documents.map { |doc| doc[
|
116
|
+
meili_ids = meili_docs.map { |doc| doc["id"] }
|
117
|
+
jekyll_ids = @documents.map { |doc| doc["id"] }
|
119
118
|
|
120
|
-
delete_obsolete_documents(config[
|
121
|
-
index_new_documents(config[
|
119
|
+
delete_obsolete_documents(config["url"], index_name, meili_ids - jekyll_ids, headers)
|
120
|
+
index_new_documents(config["url"], index_name, @documents, headers) if @documents.any?
|
122
121
|
end
|
123
122
|
|
124
123
|
def fetch_all_documents(url, index_name, headers)
|
@@ -127,15 +126,15 @@ module JekyllMeilisearch
|
|
127
126
|
limit = 1000
|
128
127
|
loop do
|
129
128
|
response = attempt_request(
|
130
|
-
|
131
|
-
HTTParty.get("#{url}/indexes/#{index_name}/documents?limit=#{limit}&offset=#{offset}", headers
|
132
|
-
timeout
|
129
|
+
lambda {
|
130
|
+
HTTParty.get("#{url}/indexes/#{index_name}/documents?limit=#{limit}&offset=#{offset}", :headers => headers,
|
131
|
+
:timeout => 30)
|
133
132
|
},
|
134
|
-
|
133
|
+
"fetching documents"
|
135
134
|
)
|
136
135
|
return nil unless response&.success?
|
137
136
|
|
138
|
-
results = JSON.parse(response.body)[
|
137
|
+
results = JSON.parse(response.body)["results"]
|
139
138
|
documents.concat(results)
|
140
139
|
break if results.size < limit
|
141
140
|
|
@@ -145,18 +144,18 @@ module JekyllMeilisearch
|
|
145
144
|
end
|
146
145
|
|
147
146
|
def delete_obsolete_documents(url, index_name, ids_to_delete, headers)
|
148
|
-
return Jekyll.logger.info
|
147
|
+
return Jekyll.logger.info "No documents to delete from Meilisearch." if ids_to_delete.empty?
|
149
148
|
|
150
149
|
Jekyll.logger.info "Deleting #{ids_to_delete.size} obsolete documents from Meilisearch..."
|
151
150
|
response = attempt_request(
|
152
|
-
|
153
|
-
HTTParty.post("#{url}/indexes/#{index_name}/documents/delete-batch", body
|
154
|
-
|
151
|
+
lambda {
|
152
|
+
HTTParty.post("#{url}/indexes/#{index_name}/documents/delete-batch", :body => ids_to_delete.to_json, :headers => headers,
|
153
|
+
:timeout => 30)
|
155
154
|
},
|
156
|
-
|
155
|
+
"deleting documents"
|
157
156
|
)
|
158
157
|
if response&.success?
|
159
|
-
Jekyll.logger.info
|
158
|
+
Jekyll.logger.info "Delete task queued successfully."
|
160
159
|
elsif response
|
161
160
|
Jekyll.logger.info "Failed to delete obsolete documents: #{response.code} - #{response.body}"
|
162
161
|
end
|
@@ -167,20 +166,20 @@ module JekyllMeilisearch
|
|
167
166
|
batch_size = 1000
|
168
167
|
documents.each_slice(batch_size) do |batch|
|
169
168
|
response = attempt_request(
|
170
|
-
|
171
|
-
HTTParty.post("#{url}/indexes/#{index_name}/documents", body
|
169
|
+
lambda {
|
170
|
+
HTTParty.post("#{url}/indexes/#{index_name}/documents", :body => batch.to_json, :headers => headers, :timeout => 30)
|
172
171
|
},
|
173
|
-
|
172
|
+
"indexing documents"
|
174
173
|
)
|
175
174
|
if response&.code == 202
|
176
175
|
if response.body
|
177
176
|
task = JSON.parse(response.body)
|
178
|
-
Jekyll.logger.info "Task queued: UID #{task[
|
177
|
+
Jekyll.logger.info "Task queued: UID #{task["taskUid"]}. Check status at #{url}/tasks/#{task["taskUid"]}"
|
179
178
|
else
|
180
|
-
Jekyll.logger.info
|
179
|
+
Jekyll.logger.info "Task queued (202), but no response body received."
|
181
180
|
end
|
182
181
|
elsif response.nil?
|
183
|
-
Jekyll.logger.info
|
182
|
+
Jekyll.logger.info "Failed to queue indexing task: No response received from Meilisearch."
|
184
183
|
else
|
185
184
|
Jekyll.logger.info "Failed to queue indexing task: #{response.code} - #{response.body}"
|
186
185
|
end
|
@@ -189,14 +188,14 @@ module JekyllMeilisearch
|
|
189
188
|
|
190
189
|
def create_index_if_missing(url, index_name, headers)
|
191
190
|
Jekyll.logger.info "Checking if index '#{index_name}' exists..."
|
192
|
-
response = HTTParty.get("#{url}/indexes/#{index_name}", headers
|
191
|
+
response = HTTParty.get("#{url}/indexes/#{index_name}", :headers => headers, :timeout => 30)
|
193
192
|
return if response.success?
|
194
193
|
|
195
194
|
if response.code == 404
|
196
195
|
Jekyll.logger.info "Index '#{index_name}' not found. Creating it..."
|
197
196
|
response = attempt_request(
|
198
|
-
-> { HTTParty.post("#{url}/indexes", body
|
199
|
-
|
197
|
+
-> { HTTParty.post("#{url}/indexes", :body => { "uid" => index_name }.to_json, :headers => headers, :timeout => 30) },
|
198
|
+
"creating index"
|
200
199
|
)
|
201
200
|
if response&.success? || response&.code == 202
|
202
201
|
Jekyll.logger.info "Index '#{index_name}' created successfully."
|
@@ -209,14 +208,14 @@ module JekyllMeilisearch
|
|
209
208
|
end
|
210
209
|
|
211
210
|
def full_index(url, index_name, documents, headers)
|
212
|
-
Jekyll.logger.info
|
211
|
+
Jekyll.logger.info "Performing full index reset as fallback..."
|
213
212
|
response = attempt_request(
|
214
|
-
-> { HTTParty.delete("#{url}/indexes/#{index_name}/documents", headers
|
215
|
-
|
213
|
+
-> { HTTParty.delete("#{url}/indexes/#{index_name}/documents", :headers => headers, :timeout => 30) },
|
214
|
+
"resetting index"
|
216
215
|
)
|
217
216
|
unless response&.success? || response&.code == 404
|
218
217
|
if response.nil?
|
219
|
-
Jekyll.logger.info
|
218
|
+
Jekyll.logger.info "Failed to reset index: No response received from Meilisearch."
|
220
219
|
else
|
221
220
|
Jekyll.logger.info "Failed to reset index: #{response.code} - #{response.body}"
|
222
221
|
end
|
data/lib/jekyll-meilisearch.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-meilisearch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- unicolored
|
@@ -98,6 +98,20 @@ dependencies:
|
|
98
98
|
- - "~>"
|
99
99
|
- !ruby/object:Gem::Version
|
100
100
|
version: '2.0'
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: nokogiri
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '1.6'
|
108
|
+
type: :development
|
109
|
+
prerelease: false
|
110
|
+
version_requirements: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - "~>"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '1.6'
|
101
115
|
- !ruby/object:Gem::Dependency
|
102
116
|
name: rake
|
103
117
|
requirement: !ruby/object:Gem::Requirement
|
@@ -112,6 +126,54 @@ dependencies:
|
|
112
126
|
- - "~>"
|
113
127
|
- !ruby/object:Gem::Version
|
114
128
|
version: '13.0'
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: rspec
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '3.0'
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '3.0'
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: rubocop-jekyll
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - "~>"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.14.0
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - "~>"
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: 0.14.0
|
157
|
+
- !ruby/object:Gem::Dependency
|
158
|
+
name: typhoeus
|
159
|
+
requirement: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0.7'
|
164
|
+
- - "<"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '2.0'
|
167
|
+
type: :development
|
168
|
+
prerelease: false
|
169
|
+
version_requirements: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0.7'
|
174
|
+
- - "<"
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '2.0'
|
115
177
|
description: This plugin incrementally indexes Jekyll collections into Meilisearch
|
116
178
|
for fast search capabilities.
|
117
179
|
email: hello@gilles.dev
|