better_seo 0.14.0 → 1.0.0.2

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +30 -0
  3. data/.rubocop_todo.yml +360 -0
  4. data/CHANGELOG.md +105 -0
  5. data/README.md +280 -180
  6. data/docs/00_OVERVIEW.md +472 -0
  7. data/docs/01_CORE_AND_CONFIGURATION.md +913 -0
  8. data/docs/02_META_TAGS_AND_OPEN_GRAPH.md +251 -0
  9. data/docs/03_STRUCTURED_DATA.md +140 -0
  10. data/docs/04_SITEMAP_AND_ROBOTS.md +131 -0
  11. data/docs/05_RAILS_INTEGRATION.md +175 -0
  12. data/docs/06_I18N_PAGE_GENERATOR.md +233 -0
  13. data/docs/07_IMAGE_OPTIMIZATION.md +260 -0
  14. data/docs/DEPENDENCIES.md +383 -0
  15. data/docs/README.md +180 -0
  16. data/docs/TESTING_STRATEGY.md +663 -0
  17. data/lib/better_seo/analytics/google_analytics.rb +83 -0
  18. data/lib/better_seo/analytics/google_tag_manager.rb +74 -0
  19. data/lib/better_seo/configuration.rb +322 -0
  20. data/lib/better_seo/dsl/base.rb +86 -0
  21. data/lib/better_seo/dsl/meta_tags.rb +55 -0
  22. data/lib/better_seo/dsl/open_graph.rb +105 -0
  23. data/lib/better_seo/dsl/twitter_cards.rb +129 -0
  24. data/lib/better_seo/errors.rb +31 -0
  25. data/lib/better_seo/generators/amp_generator.rb +77 -0
  26. data/lib/better_seo/generators/breadcrumbs_generator.rb +127 -0
  27. data/lib/better_seo/generators/canonical_url_manager.rb +100 -0
  28. data/lib/better_seo/generators/meta_tags_generator.rb +101 -0
  29. data/lib/better_seo/generators/open_graph_generator.rb +110 -0
  30. data/lib/better_seo/generators/robots_txt_generator.rb +96 -0
  31. data/lib/better_seo/generators/twitter_cards_generator.rb +102 -0
  32. data/lib/better_seo/image/optimizer.rb +145 -0
  33. data/lib/better_seo/rails/helpers/controller_helpers.rb +120 -0
  34. data/lib/better_seo/rails/helpers/seo_helper.rb +172 -0
  35. data/lib/better_seo/rails/helpers/structured_data_helper.rb +123 -0
  36. data/lib/better_seo/rails/model_helpers.rb +62 -0
  37. data/lib/better_seo/rails/railtie.rb +22 -0
  38. data/lib/better_seo/sitemap/builder.rb +65 -0
  39. data/lib/better_seo/sitemap/generator.rb +57 -0
  40. data/lib/better_seo/sitemap/sitemap_index.rb +73 -0
  41. data/lib/better_seo/sitemap/url_entry.rb +155 -0
  42. data/lib/better_seo/structured_data/article.rb +55 -0
  43. data/lib/better_seo/structured_data/base.rb +74 -0
  44. data/lib/better_seo/structured_data/breadcrumb_list.rb +49 -0
  45. data/lib/better_seo/structured_data/event.rb +205 -0
  46. data/lib/better_seo/structured_data/faq_page.rb +55 -0
  47. data/lib/better_seo/structured_data/generator.rb +75 -0
  48. data/lib/better_seo/structured_data/how_to.rb +96 -0
  49. data/lib/better_seo/structured_data/local_business.rb +94 -0
  50. data/lib/better_seo/structured_data/organization.rb +67 -0
  51. data/lib/better_seo/structured_data/person.rb +51 -0
  52. data/lib/better_seo/structured_data/product.rb +123 -0
  53. data/lib/better_seo/structured_data/recipe.rb +134 -0
  54. data/lib/better_seo/validators/seo_recommendations.rb +165 -0
  55. data/lib/better_seo/validators/seo_validator.rb +205 -0
  56. data/lib/better_seo/version.rb +1 -1
  57. data/lib/better_seo.rb +1 -0
  58. data/lib/generators/better_seo/install_generator.rb +21 -0
  59. data/lib/generators/better_seo/templates/README +29 -0
  60. data/lib/generators/better_seo/templates/better_seo.rb +40 -0
  61. metadata +57 -2
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterSeo
4
+ module Validators
5
+ class SeoRecommendations
6
+ def generate_recommendations(validation_result)
7
+ recommendations = []
8
+
9
+ recommendations += recommend_title_improvements(validation_result[:title])
10
+ recommendations += recommend_description_improvements(validation_result[:description])
11
+ recommendations += recommend_heading_improvements(validation_result[:headings])
12
+ recommendations += recommend_image_improvements(validation_result[:images])
13
+
14
+ # Sort by priority: high, medium, low
15
+ priority_order = { high: 0, medium: 1, low: 2 }
16
+ recommendations.sort_by { |r| priority_order[r[:priority]] }
17
+ end
18
+
19
+ def recommend_title_improvements(title_result)
20
+ return [] if title_result[:valid]
21
+
22
+ recommendations = []
23
+ length = title_result[:length]
24
+
25
+ if length.zero?
26
+ recommendations << {
27
+ category: "Title",
28
+ priority: :high,
29
+ action: "Add a page title",
30
+ details: "A title tag is required for SEO. Aim for 30-60 characters."
31
+ }
32
+ elsif length < 30
33
+ needed = 30 - length
34
+ recommendations << {
35
+ category: "Title",
36
+ priority: :high,
37
+ action: "Make title longer",
38
+ details: "Add approximately #{needed} more characters to reach the optimal length (30-60 chars)."
39
+ }
40
+ elsif length > 60
41
+ excess = length - 60
42
+ recommendations << {
43
+ category: "Title",
44
+ priority: :medium,
45
+ action: "Make title shorter",
46
+ details: "Reduce by approximately #{excess} characters to stay within optimal length (30-60 chars)."
47
+ }
48
+ end
49
+
50
+ recommendations
51
+ end
52
+
53
+ def recommend_description_improvements(desc_result)
54
+ return [] if desc_result[:valid]
55
+
56
+ recommendations = []
57
+ length = desc_result[:length]
58
+
59
+ if length.zero?
60
+ recommendations << {
61
+ category: "Meta Description",
62
+ priority: :high,
63
+ action: "Add a meta description",
64
+ details: "Meta descriptions help search engines understand your content. Aim for 120-160 characters."
65
+ }
66
+ elsif length < 120
67
+ needed = 120 - length
68
+ recommendations << {
69
+ category: "Meta Description",
70
+ priority: :medium,
71
+ action: "Add more detail to description",
72
+ details: "Add approximately #{needed} more characters to provide more context (optimal: 120-160 chars)."
73
+ }
74
+ elsif length > 160
75
+ excess = length - 160
76
+ recommendations << {
77
+ category: "Meta Description",
78
+ priority: :medium,
79
+ action: "Condense description",
80
+ details: "Reduce by approximately #{excess} characters to prevent truncation in search results."
81
+ }
82
+ end
83
+
84
+ recommendations
85
+ end
86
+
87
+ def recommend_heading_improvements(headings_result)
88
+ return [] if headings_result[:valid]
89
+
90
+ recommendations = []
91
+ h1_count = headings_result[:h1_count]
92
+
93
+ if h1_count.zero?
94
+ recommendations << {
95
+ category: "Headings",
96
+ priority: :high,
97
+ action: "Add an H1 heading",
98
+ details: "Every page should have exactly one H1 tag that describes the main topic."
99
+ }
100
+ elsif h1_count > 1
101
+ recommendations << {
102
+ category: "Headings",
103
+ priority: :high,
104
+ action: "Use only one H1 heading",
105
+ details: "Found #{h1_count} H1 tags. Use H2-H6 for subheadings instead."
106
+ }
107
+ end
108
+
109
+ if headings_result[:total_headings].zero?
110
+ recommendations << {
111
+ category: "Headings",
112
+ priority: :medium,
113
+ action: "Add heading structure",
114
+ details: "Use headings (H1-H6) to organize content and improve readability."
115
+ }
116
+ end
117
+
118
+ recommendations
119
+ end
120
+
121
+ def recommend_image_improvements(images_result)
122
+ return [] if images_result[:valid]
123
+
124
+ recommendations = []
125
+
126
+ if images_result[:images_without_alt].positive?
127
+ count = images_result[:images_without_alt]
128
+ recommendations << {
129
+ category: "Images",
130
+ priority: :medium,
131
+ action: "Add alt text to images",
132
+ details: "#{count} image#{"s" if count > 1} missing alt text. Alt text improves accessibility and SEO."
133
+ }
134
+ end
135
+
136
+ recommendations
137
+ end
138
+
139
+ def format_recommendations(recommendations)
140
+ return "# SEO Recommendations\n\nNo recommendations - your SEO is optimal! ✓" if recommendations.empty?
141
+
142
+ output = []
143
+ output << "# SEO Recommendations\n"
144
+
145
+ # Group by priority
146
+ grouped = recommendations.group_by { |r| r[:priority] }
147
+
148
+ %i[high medium low].each do |priority|
149
+ next unless grouped[priority]
150
+
151
+ output << "## #{priority.to_s.capitalize} Priority\n"
152
+
153
+ grouped[priority].each do |rec|
154
+ output << "### #{rec[:category]}"
155
+ output << "**Action:** #{rec[:action]}\n"
156
+ output << "**Details:** #{rec[:details]}\n" if rec[:details]
157
+ output << ""
158
+ end
159
+ end
160
+
161
+ output.join("\n")
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterSeo
4
+ module Validators
5
+ class SeoValidator
6
+ TITLE_MIN_LENGTH = 30
7
+ TITLE_MAX_LENGTH = 60
8
+ DESCRIPTION_MIN_LENGTH = 120
9
+ DESCRIPTION_MAX_LENGTH = 160
10
+
11
+ def check_title(title)
12
+ return { valid: false, score: 0, message: "Title is required", length: 0 } if title.nil? || title.empty?
13
+
14
+ length = title.length
15
+
16
+ if length.between?(TITLE_MIN_LENGTH, TITLE_MAX_LENGTH)
17
+ { valid: true, score: 100, message: "Title length is optimal", length: length }
18
+ elsif length < TITLE_MIN_LENGTH
19
+ score = (length.to_f / TITLE_MIN_LENGTH * 70).to_i
20
+ { valid: false, score: score,
21
+ message: "Title is too short (minimum #{TITLE_MIN_LENGTH} characters recommended)", length: length }
22
+ else
23
+ score = [100 - ((length - TITLE_MAX_LENGTH) * 2), 50].max
24
+ { valid: false, score: score,
25
+ message: "Title is too long (maximum #{TITLE_MAX_LENGTH} characters recommended)", length: length }
26
+ end
27
+ end
28
+
29
+ def check_description(description)
30
+ if description.nil? || description.empty?
31
+ return { valid: false, score: 0, message: "Description is required",
32
+ length: 0 }
33
+ end
34
+
35
+ length = description.length
36
+
37
+ if length.between?(DESCRIPTION_MIN_LENGTH, DESCRIPTION_MAX_LENGTH)
38
+ { valid: true, score: 100, message: "Description length is optimal", length: length }
39
+ elsif length < DESCRIPTION_MIN_LENGTH
40
+ score = (length.to_f / DESCRIPTION_MIN_LENGTH * 70).to_i
41
+ { valid: false, score: score,
42
+ message: "Description is too short (minimum #{DESCRIPTION_MIN_LENGTH} characters recommended)", length: length }
43
+ else
44
+ score = [100 - (length - DESCRIPTION_MAX_LENGTH), 50].max
45
+ { valid: false, score: score,
46
+ message: "Description is too long (maximum #{DESCRIPTION_MAX_LENGTH} characters recommended)", length: length }
47
+ end
48
+ end
49
+
50
+ def check_headings(html)
51
+ h1_count = html.scan(/<h1[^>]*>/).size
52
+ h2_count = html.scan(/<h2[^>]*>/).size
53
+ h3_count = html.scan(/<h3[^>]*>/).size
54
+ h4_count = html.scan(/<h4[^>]*>/).size
55
+ h5_count = html.scan(/<h5[^>]*>/).size
56
+ h6_count = html.scan(/<h6[^>]*>/).size
57
+
58
+ total = h1_count + h2_count + h3_count + h4_count + h5_count + h6_count
59
+
60
+ result = {
61
+ h1_count: h1_count,
62
+ h2_count: h2_count,
63
+ h3_count: h3_count,
64
+ total_headings: total
65
+ }
66
+
67
+ if h1_count.zero?
68
+ result.merge(valid: false, score: 50, message: "Missing H1 heading (exactly one H1 required)")
69
+ elsif h1_count > 1
70
+ result.merge(valid: false, score: 70, message: "Multiple H1 headings found (only one H1 recommended)")
71
+ elsif total.zero?
72
+ result.merge(valid: false, score: 0, message: "No headings found")
73
+ else
74
+ result.merge(valid: true, score: 100, message: "Heading structure is optimal")
75
+ end
76
+ end
77
+
78
+ def check_images(html)
79
+ # Find all img tags
80
+ images = html.scan(/<img[^>]*>/)
81
+ total = images.size
82
+
83
+ if total.zero?
84
+ return { valid: true, score: 100, total_images: 0, images_with_alt: 0, images_without_alt: 0,
85
+ message: "No images to validate" }
86
+ end
87
+
88
+ # Count images with alt text
89
+ images_with_alt = images.count { |img| img =~ /alt=["'][^"']+["']/ }
90
+ images_without_alt = total - images_with_alt
91
+
92
+ result = {
93
+ total_images: total,
94
+ images_with_alt: images_with_alt,
95
+ images_without_alt: images_without_alt
96
+ }
97
+
98
+ if images_without_alt.zero?
99
+ result.merge(valid: true, score: 100, message: "All images have alt text")
100
+ else
101
+ score = (images_with_alt.to_f / total * 100).to_i
102
+ result.merge(valid: false, score: score, message: "#{images_without_alt} image(s) missing alt text")
103
+ end
104
+ end
105
+
106
+ def validate_page(html)
107
+ # Extract title
108
+ title_match = html.match(%r{<title[^>]*>(.*?)</title>}m)
109
+ title = title_match ? title_match[1].strip : nil
110
+
111
+ # Extract meta description
112
+ desc_match = html.match(/<meta\s+name=["']description["']\s+content=["'](.*?)["']/i)
113
+ description = desc_match ? desc_match[1] : nil
114
+
115
+ # Run all checks
116
+ title_result = check_title(title)
117
+ description_result = check_description(description)
118
+ headings_result = check_headings(html)
119
+ images_result = check_images(html)
120
+
121
+ # Calculate overall score (weighted average)
122
+ overall_score = (
123
+ (title_result[:score] * 0.3) +
124
+ (description_result[:score] * 0.3) +
125
+ (headings_result[:score] * 0.2) +
126
+ (images_result[:score] * 0.2)
127
+ ).to_i
128
+
129
+ # Collect issues
130
+ issues = []
131
+ issues << "Title: #{title_result[:message]}" unless title_result[:valid]
132
+ issues << "Description: #{description_result[:message]}" unless description_result[:valid]
133
+ issues << "Headings: #{headings_result[:message]}" unless headings_result[:valid]
134
+ issues << "Images: #{images_result[:message]}" unless images_result[:valid]
135
+
136
+ {
137
+ overall_score: overall_score,
138
+ title: title_result,
139
+ description: description_result,
140
+ headings: headings_result,
141
+ images: images_result,
142
+ issues: issues
143
+ }
144
+ end
145
+
146
+ def generate_report(validation)
147
+ report = []
148
+ report << ("=" * 60)
149
+ report << "SEO Validation Report"
150
+ report << ("=" * 60)
151
+ report << ""
152
+ report << "Overall Score: #{validation[:overall_score]}/100"
153
+ report << ""
154
+
155
+ # Title section
156
+ report << "Title:"
157
+ report << " Status: #{validation[:title][:valid] ? "✓" : "✗"}"
158
+ report << " Score: #{validation[:title][:score]}/100"
159
+ report << " Length: #{validation[:title][:length]} chars"
160
+ report << " Message: #{validation[:title][:message]}"
161
+ report << ""
162
+
163
+ # Description section
164
+ report << "Description:"
165
+ report << " Status: #{validation[:description][:valid] ? "✓" : "✗"}"
166
+ report << " Score: #{validation[:description][:score]}/100"
167
+ report << " Length: #{validation[:description][:length]} chars"
168
+ report << " Message: #{validation[:description][:message]}"
169
+ report << ""
170
+
171
+ # Headings section
172
+ report << "Headings:"
173
+ report << " Status: #{validation[:headings][:valid] ? "✓" : "✗"}"
174
+ report << " Score: #{validation[:headings][:score]}/100"
175
+ report << " H1 Count: #{validation[:headings][:h1_count]}"
176
+ report << " Total Headings: #{validation[:headings][:total_headings]}"
177
+ report << " Message: #{validation[:headings][:message]}"
178
+ report << ""
179
+
180
+ # Images section
181
+ report << "Images:"
182
+ report << " Status: #{validation[:images][:valid] ? "✓" : "✗"}"
183
+ report << " Score: #{validation[:images][:score]}/100"
184
+ report << " Total Images: #{validation[:images][:total_images]}"
185
+ report << " With Alt: #{validation[:images][:images_with_alt]}"
186
+ report << " Without Alt: #{validation[:images][:images_without_alt]}"
187
+ report << " Message: #{validation[:images][:message]}"
188
+ report << ""
189
+
190
+ # Issues section
191
+ if validation[:issues].any?
192
+ report << "Issues:"
193
+ validation[:issues].each do |issue|
194
+ report << " - #{issue}"
195
+ end
196
+ report << ""
197
+ end
198
+
199
+ report << ("=" * 60)
200
+
201
+ report.join("\n")
202
+ end
203
+ end
204
+ end
205
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BetterSeo
4
- VERSION = "0.14.0"
4
+ VERSION = "1.0.0.2"
5
5
  end
data/lib/better_seo.rb CHANGED
@@ -42,6 +42,7 @@ require_relative "better_seo/rails/helpers/seo_helper"
42
42
  require_relative "better_seo/rails/helpers/structured_data_helper"
43
43
  require_relative "better_seo/rails/helpers/controller_helpers"
44
44
  require_relative "better_seo/rails/model_helpers"
45
+ require_relative "better_seo/rails/railtie" if defined?(Rails::Railtie)
45
46
 
46
47
  module BetterSeo
47
48
  class << self
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module BetterSeo
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Creates a BetterSeo initializer file"
11
+
12
+ def copy_initializer
13
+ template "better_seo.rb", "config/initializers/better_seo.rb"
14
+ end
15
+
16
+ def show_readme
17
+ readme "README" if behavior == :invoke
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ ===============================================================================
2
+
3
+ BetterSeo has been installed successfully!
4
+
5
+ Configuration file created at: config/initializers/better_seo.rb
6
+
7
+ Next steps:
8
+
9
+ 1. Edit the configuration file to match your site settings
10
+ 2. Include BetterSeo::Rails::Helpers::ControllerHelpers in your controllers
11
+ (or use ApplicationController if you want it available everywhere)
12
+
13
+ 3. Use the helpers in your views:
14
+ - <%= seo_tags %> for complete SEO tags
15
+ - <%= seo_meta_tags %> for meta tags only
16
+ - <%= seo_open_graph_tags %> for Open Graph tags
17
+ - <%= seo_twitter_tags %> for Twitter Cards
18
+ - <%= structured_data_tag(:organization, ...) %> for structured data
19
+
20
+ 4. In your controllers, set page-specific SEO data:
21
+ - set_page_title("My Page")
22
+ - set_page_description("Description")
23
+ - set_page_keywords(["keyword1", "keyword2"])
24
+ - set_page_image("https://example.com/image.jpg")
25
+ - set_canonical("https://example.com/canonical-url")
26
+
27
+ For more information, visit: https://github.com/alessiobussolari/better_seo
28
+
29
+ ===============================================================================
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ BetterSeo.configure do |config|
4
+ # Site-wide settings
5
+ config.site_name = "My Site"
6
+ config.default_locale = :en
7
+ config.available_locales = %i[en it fr de es]
8
+
9
+ # Meta tags configuration
10
+ config.meta_tags.default_title = "Default Title"
11
+ config.meta_tags.title_separator = " | "
12
+ config.meta_tags.append_site_name = true
13
+ config.meta_tags.default_description = "Default description for your website"
14
+ config.meta_tags.default_keywords = %w[keyword1 keyword2 keyword3]
15
+ config.meta_tags.default_author = "Your Name or Company"
16
+
17
+ # Open Graph configuration
18
+ config.open_graph.enabled = true
19
+ config.open_graph.site_name = "My Site"
20
+ config.open_graph.default_type = "website"
21
+ config.open_graph.default_locale = "en_US"
22
+ config.open_graph.default_image.url = "https://example.com/default-og-image.jpg"
23
+ config.open_graph.default_image.width = 1200
24
+ config.open_graph.default_image.height = 630
25
+ config.open_graph.default_image.alt = "Default image description"
26
+
27
+ # Twitter Cards configuration
28
+ config.twitter.enabled = true
29
+ config.twitter.site = "@yoursite"
30
+ config.twitter.creator = "@yourcreator"
31
+ config.twitter.card_type = "summary_large_image"
32
+
33
+ # Structured Data configuration
34
+ config.structured_data.enabled = true
35
+ # config.structured_data.organization = {
36
+ # name: "My Organization",
37
+ # url: "https://example.com",
38
+ # logo: "https://example.com/logo.png"
39
+ # }
40
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_seo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 1.0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - alessiobussolari
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-23 00:00:00.000000000 Z
11
+ date: 2025-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -77,13 +77,67 @@ extra_rdoc_files: []
77
77
  files:
78
78
  - ".rspec"
79
79
  - ".rubocop.yml"
80
+ - ".rubocop_todo.yml"
80
81
  - CHANGELOG.md
81
82
  - CODE_OF_CONDUCT.md
82
83
  - LICENSE.txt
83
84
  - README.md
84
85
  - Rakefile
86
+ - docs/00_OVERVIEW.md
87
+ - docs/01_CORE_AND_CONFIGURATION.md
88
+ - docs/02_META_TAGS_AND_OPEN_GRAPH.md
89
+ - docs/03_STRUCTURED_DATA.md
90
+ - docs/04_SITEMAP_AND_ROBOTS.md
91
+ - docs/05_RAILS_INTEGRATION.md
92
+ - docs/06_I18N_PAGE_GENERATOR.md
93
+ - docs/07_IMAGE_OPTIMIZATION.md
94
+ - docs/DEPENDENCIES.md
95
+ - docs/README.md
96
+ - docs/TESTING_STRATEGY.md
85
97
  - lib/better_seo.rb
98
+ - lib/better_seo/analytics/google_analytics.rb
99
+ - lib/better_seo/analytics/google_tag_manager.rb
100
+ - lib/better_seo/configuration.rb
101
+ - lib/better_seo/dsl/base.rb
102
+ - lib/better_seo/dsl/meta_tags.rb
103
+ - lib/better_seo/dsl/open_graph.rb
104
+ - lib/better_seo/dsl/twitter_cards.rb
105
+ - lib/better_seo/errors.rb
106
+ - lib/better_seo/generators/amp_generator.rb
107
+ - lib/better_seo/generators/breadcrumbs_generator.rb
108
+ - lib/better_seo/generators/canonical_url_manager.rb
109
+ - lib/better_seo/generators/meta_tags_generator.rb
110
+ - lib/better_seo/generators/open_graph_generator.rb
111
+ - lib/better_seo/generators/robots_txt_generator.rb
112
+ - lib/better_seo/generators/twitter_cards_generator.rb
113
+ - lib/better_seo/image/optimizer.rb
114
+ - lib/better_seo/rails/helpers/controller_helpers.rb
115
+ - lib/better_seo/rails/helpers/seo_helper.rb
116
+ - lib/better_seo/rails/helpers/structured_data_helper.rb
117
+ - lib/better_seo/rails/model_helpers.rb
118
+ - lib/better_seo/rails/railtie.rb
119
+ - lib/better_seo/sitemap/builder.rb
120
+ - lib/better_seo/sitemap/generator.rb
121
+ - lib/better_seo/sitemap/sitemap_index.rb
122
+ - lib/better_seo/sitemap/url_entry.rb
123
+ - lib/better_seo/structured_data/article.rb
124
+ - lib/better_seo/structured_data/base.rb
125
+ - lib/better_seo/structured_data/breadcrumb_list.rb
126
+ - lib/better_seo/structured_data/event.rb
127
+ - lib/better_seo/structured_data/faq_page.rb
128
+ - lib/better_seo/structured_data/generator.rb
129
+ - lib/better_seo/structured_data/how_to.rb
130
+ - lib/better_seo/structured_data/local_business.rb
131
+ - lib/better_seo/structured_data/organization.rb
132
+ - lib/better_seo/structured_data/person.rb
133
+ - lib/better_seo/structured_data/product.rb
134
+ - lib/better_seo/structured_data/recipe.rb
135
+ - lib/better_seo/validators/seo_recommendations.rb
136
+ - lib/better_seo/validators/seo_validator.rb
86
137
  - lib/better_seo/version.rb
138
+ - lib/generators/better_seo/install_generator.rb
139
+ - lib/generators/better_seo/templates/README
140
+ - lib/generators/better_seo/templates/better_seo.rb
87
141
  - sig/better_seo.rbs
88
142
  homepage: https://github.com/alessiobussolari/better_seo
89
143
  licenses:
@@ -92,6 +146,7 @@ metadata:
92
146
  homepage_uri: https://github.com/alessiobussolari/better_seo
93
147
  source_code_uri: https://github.com/alessiobussolari/better_seo
94
148
  changelog_uri: https://github.com/alessiobussolari/better_seo/blob/main/CHANGELOG.md
149
+ rubygems_mfa_required: 'true'
95
150
  post_install_message:
96
151
  rdoc_options: []
97
152
  require_paths: