seowebchecker-seoaudit-sdk 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 216cda4028ed53965356f9985598f5a308bb840a6b9aa954bfc99681aee75de0
4
+ data.tar.gz: 1f88f524ef991768aebb6e758b280ac61ef2dc103b600fcf75d05e962ce43cda
5
+ SHA512:
6
+ metadata.gz: 28a065f81d2c36e89859f691ede8471c230eb71ef4e6e8a4c072acd121a8d166b1e3806533fd66bd9a57f4c6b7734dc67c5f5327f9a0bac0173d3bcc69c04c2f
7
+ data.tar.gz: 7e9bc40bcc26028967f11b473d9bfa2e1b6510125501ab70230eb131d6c763e394813f618e6febc16163c33d93aaddfbbb4ca7566a99c2eb87c1b20df058f37d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SEOWebChecker (https://seowebchecker.com)
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # seowebchecker-seoaudit-sdk (Ruby)
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/seowebchecker-seoaudit-sdk.svg)](https://rubygems.org/gems/seowebchecker-seoaudit-sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Official Site](https://img.shields.io/badge/Official%20Site-seowebchecker.com-indigo)](https://seowebchecker.com)
6
+
7
+ Lightweight open-source Ruby client SDK and CLI tool for full website SEO audits, on-page analysis, and technical diagnostics.
8
+
9
+ Official website: **[https://seowebchecker.com](https://seowebchecker.com)**
10
+
11
+ ## Installation
12
+
13
+ Add to your `Gemfile`:
14
+
15
+ ```ruby
16
+ gem 'seowebchecker-seoaudit-sdk'
17
+ ```
18
+
19
+ Or install via `gem`:
20
+
21
+ ```bash
22
+ gem install seowebchecker-seoaudit-sdk
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```ruby
28
+ require 'seowebchecker_seoaudit'
29
+
30
+ auditor = SeoWebChecker::SeoAudit::Auditor.new
31
+ result = auditor.audit('https://example.com')
32
+
33
+ puts "Score: #{result[:score][:overall]}/100 (Grade: #{result[:score][:grade]})"
34
+ puts "Passed: #{result[:stats][:passed]}, Warnings: #{result[:stats][:warnings]}, Errors: #{result[:stats][:errors]}"
35
+ ```
36
+
37
+ ## CLI Usage
38
+
39
+ ```bash
40
+ seowebchecker-audit https://example.com --format json
41
+ seowebchecker-audit https://example.com --min-score 85
42
+ ```
43
+
44
+ ## License
45
+
46
+ MIT License © 2026 [SEOWebChecker](https://seowebchecker.com).
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require_relative "../lib/seowebchecker_seoaudit"
6
+
7
+ target_url = ARGV[0]
8
+
9
+ if target_url.nil? || ARGV.include?("--help") || ARGV.include?("-h")
10
+ puts <<~HELP
11
+ SEOWebChecker CLI — Website SEO Audit Tool (Ruby)
12
+ Official Website: https://seowebchecker.com
13
+
14
+ Usage:
15
+ seowebchecker-audit <url> [options]
16
+
17
+ Options:
18
+ --format <pretty|json> Output format (default: pretty)
19
+ --min-score <number> Minimum acceptable score (exit 1 if lower)
20
+ --help, -h Show this help message
21
+ --version, -v Show version
22
+ HELP
23
+ exit(0)
24
+ end
25
+
26
+ if ARGV.include?("--version") || ARGV.include?("-v")
27
+ puts "seowebchecker-seoaudit-sdk v#{SeoWebChecker::SeoAudit::VERSION}"
28
+ exit(0)
29
+ end
30
+
31
+ auditor = SeoWebChecker::SeoAudit::Auditor.new
32
+ result = auditor.audit(target_url)
33
+
34
+ if ARGV.include?("--format") && ARGV[ARGV.index("--format") + 1] == "json"
35
+ puts JSON.pretty_generate(result)
36
+ else
37
+ score = result[:score][:overall]
38
+ grade = result[:score][:grade]
39
+
40
+ puts "=" * 65
41
+ puts " SEOWebChecker SEO Audit Report: #{result[:url]}"
42
+ puts " Official: https://seowebchecker.com | Generated: #{result[:timestamp]}"
43
+ puts "=" * 65
44
+ puts "\n Overall Score: #{score}/100 | Grade: #{grade}"
45
+ puts " Checks: #{result[:stats][:passed]} Passed, #{result[:stats][:warnings]} Warnings, #{result[:stats][:errors]} Errors\n\n"
46
+
47
+ errors = result[:issues].select { |i| i[:severity] == "error" }
48
+ if errors.any?
49
+ puts "[!] High Priority Issues (Errors):"
50
+ errors.each do |e|
51
+ puts " ✖ #{e[:title]}: #{e[:recommendation]}"
52
+ end
53
+ end
54
+ end
55
+
56
+ min_score_idx = ARGV.index("--min-score")
57
+ if min_score_idx
58
+ min_score = ARGV[min_score_idx + 1].to_i
59
+ if result[:score][:overall] < min_score
60
+ warn "\n[CI/CD ERROR] Score #{result[:score][:overall]} is below minimum requirement of #{min_score}."
61
+ exit(1)
62
+ end
63
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "time"
7
+
8
+ module SeoWebChecker
9
+ module SeoAudit
10
+ class Auditor
11
+ attr_accessor :user_agent, :timeout
12
+
13
+ DEFAULT_UA = "SEOWebChecker-RubyBot/1.0 (+https://seowebchecker.com)"
14
+
15
+ def initialize(user_agent: DEFAULT_UA, timeout: 15)
16
+ @user_agent = user_agent
17
+ @timeout = timeout
18
+ end
19
+
20
+ def audit(url)
21
+ url = "https://#{url}" unless url.start_with?("http://", "https://")
22
+ uri = URI.parse(url)
23
+
24
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
25
+ http = Net::HTTP.new(uri.host, uri.port)
26
+ http.use_ssl = (uri.scheme == "https")
27
+ http.open_timeout = @timeout
28
+ http.read_timeout = @timeout
29
+
30
+ req = Net::HTTP::Get.new(uri.request_uri, {
31
+ "User-Agent" => @user_agent,
32
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
33
+ })
34
+
35
+ res = http.request(req)
36
+ elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000.0).round(2)
37
+
38
+ audit_html(res.body || "", url: url, response_time_ms: elapsed_ms, status_code: res.code.to_i)
39
+ end
40
+
41
+ def audit_html(html, url: "https://seowebchecker.com", response_time_ms: 120.0, status_code: 200)
42
+ issues = []
43
+
44
+ # 1. Title
45
+ title_match = html.match(/<title[^>]*>(.*?)<\/title>/im)
46
+ title = title_match ? title_match[1].gsub(/\s+/, " ").strip : nil
47
+ title_len = title ? title.length : 0
48
+
49
+ if title.nil? || title.empty?
50
+ issues << { id: "meta-title-missing", category: "meta", severity: "error", title: "Missing Title Tag", message: "No <title> tag found.", recommendation: "Add a title tag between 30 and 60 characters." }
51
+ elsif title_len < 30
52
+ issues << { id: "meta-title-short", category: "meta", severity: "warning", title: "Title Too Short", message: "Title has #{title_len} characters.", recommendation: "Expand title to 30-60 characters." }
53
+ elsif title_len > 65
54
+ issues << { id: "meta-title-long", category: "meta", severity: "warning", title: "Title Too Long", message: "Title has #{title_len} characters.", recommendation: "Shorten title to under 60 characters." }
55
+ else
56
+ issues << { id: "meta-title-pass", category: "meta", severity: "pass", title: "Optimal Title Length", message: "Title length is #{title_len} characters.", recommendation: "Keep clear title." }
57
+ end
58
+
59
+ # 2. Description
60
+ desc_match = html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']*)["']/im) ||
61
+ html.match(/<meta[^>]*content=["']([^"']*)["'][^>]*name=["']description["']/im)
62
+ desc = desc_match ? desc_match[1].strip : nil
63
+ desc_len = desc ? desc.length : 0
64
+
65
+ if desc.nil? || desc.empty?
66
+ issues << { id: "meta-desc-missing", category: "meta", severity: "error", title: "Missing Meta Description", message: "No description tag found.", recommendation: "Add meta description between 120 and 160 characters." }
67
+ elsif desc_len < 70
68
+ issues << { id: "meta-desc-short", category: "meta", severity: "warning", title: "Description Too Short", message: "Description has #{desc_len} characters.", recommendation: "Expand description to 120-160 characters." }
69
+ else
70
+ issues << { id: "meta-desc-pass", category: "meta", severity: "pass", title: "Optimal Description", message: "Description has #{desc_len} characters.", recommendation: "Maintain quality description." }
71
+ end
72
+
73
+ # 3. Viewport & Canonical
74
+ has_viewport = html =~ /<meta[^>]*name=["']viewport["']/i
75
+ if has_viewport
76
+ issues << { id: "meta-viewport-pass", category: "meta", severity: "pass", title: "Viewport Configured", message: "Mobile viewport meta tag detected.", recommendation: "Mobile responsive." }
77
+ else
78
+ issues << { id: "meta-viewport-missing", category: "meta", severity: "error", title: "Missing Viewport", message: "No mobile viewport tag.", recommendation: "Add viewport meta tag for mobile SEO." }
79
+ end
80
+
81
+ has_canonical = html =~ /<link[^>]*rel=["']canonical["']/i
82
+ if has_canonical
83
+ issues << { id: "meta-canonical-pass", category: "meta", severity: "pass", title: "Canonical Present", message: "Canonical tag detected.", recommendation: "Canonical URL active." }
84
+ else
85
+ issues << { id: "meta-canonical-missing", category: "meta", severity: "warning", title: "Missing Canonical Tag", message: "No canonical link.", recommendation: "Add canonical URL to avoid duplicates." }
86
+ end
87
+
88
+ # 4. Headings & Content
89
+ h1_tags = html.scan(/<h1[^>]*>(.*?)<\/h1>/im).flatten.map { |h| h.gsub(/<[^>]+>/, "").strip }
90
+ clean_text = html.gsub(/<(script|style)[^>]*>.*?<\/\1>/im, " ").gsub(/<[^>]+>/, " ")
91
+ words = clean_text.scan(/\b[a-zA-Z0-9_\'-]{2,}\b/)
92
+ word_count = words.length
93
+
94
+ if h1_tags.empty?
95
+ issues << { id: "content-h1-missing", category: "content", severity: "error", title: "Missing <h1> Tag", message: "No <h1> heading found.", recommendation: "Add a single <h1> heading." }
96
+ elsif h1_tags.length == 1
97
+ issues << { id: "content-h1-pass", category: "content", severity: "pass", title: "Single <h1> Tag Configured", message: "H1 heading: '#{h1_tags.first}'.", recommendation: "Good hierarchy." }
98
+ else
99
+ issues << { id: "content-h1-multiple", category: "content", severity: "warning", title: "Multiple <h1> Tags (#{h1_tags.length})", message: "Found #{h1_tags.length} <h1> tags.", recommendation: "Consolidate to a single <h1>." }
100
+ end
101
+
102
+ if word_count < 100
103
+ issues << { id: "content-thin", category: "content", severity: "error", title: "Thin Content", message: "Page contains only #{word_count} words.", recommendation: "Expand content to 300+ words." }
104
+ else
105
+ issues << { id: "content-words-pass", category: "content", severity: "pass", title: "Adequate Word Count", message: "Page contains #{word_count} words.", recommendation: "Good text volume." }
106
+ end
107
+
108
+ # 5. Images
109
+ images = html.scan(/<img\s+([^>]*?)>/im).flatten
110
+ missing_alt = images.count { |attrs| attrs !~ /alt=["']/i }
111
+ if !images.empty? && missing_alt > 0
112
+ issues << { id: "images-missing-alt", category: "images", severity: "error", title: "#{missing_alt} Images Missing Alt Text", message: "#{missing_alt} of #{images.length} images lack alt attributes.", recommendation: "Add descriptive alt attributes." }
113
+ elsif !images.empty?
114
+ issues << { id: "images-alt-pass", category: "images", severity: "pass", title: "All Images Have Alt Text", message: "All #{images.length} images have alt tags.", recommendation: "Great image SEO." }
115
+ end
116
+
117
+ # 6. Technical HTTPS
118
+ is_https = url.downcase.start_with?("https://")
119
+ if is_https
120
+ issues << { id: "tech-https-pass", category: "technical", severity: "pass", title: "Secure HTTPS", message: "Connection secured with SSL/TLS.", recommendation: "Keep certificate updated." }
121
+ else
122
+ issues << { id: "tech-not-https", category: "technical", severity: "error", title: "Insecure HTTP", message: "Site does not enforce HTTPS.", recommendation: "Install SSL certificate." }
123
+ end
124
+
125
+ # Compute Score
126
+ passed_count = issues.count { |i| i[:severity] == "pass" }
127
+ warning_count = issues.count { |i| i[:severity] == "warning" }
128
+ error_count = issues.count { |i| i[:severity] == "error" }
129
+
130
+ total = issues.length
131
+ raw_score = total > 0 ? ((passed_count.to_f / total) * 100.0).round : 80
132
+ raw_score -= (error_count * 10) + (warning_count * 3)
133
+ final_score = [[raw_score, 0].max, 100].min
134
+
135
+ grade = case final_score
136
+ when 95..100 then "A+"
137
+ when 90...95 then "A"
138
+ when 80...90 then "B"
139
+ when 70...80 then "C"
140
+ when 60...70 then "D"
141
+ else "F"
142
+ end
143
+
144
+ {
145
+ url: url,
146
+ timestamp: Time.now.utc.iso8601,
147
+ score: {
148
+ overall: final_score,
149
+ grade: grade
150
+ },
151
+ stats: {
152
+ total: total,
153
+ passed: passed_count,
154
+ warnings: warning_count,
155
+ errors: error_count
156
+ },
157
+ meta: {
158
+ title: title,
159
+ title_length: title_len,
160
+ description: desc,
161
+ description_length: desc_len
162
+ },
163
+ content: {
164
+ word_count: word_count,
165
+ h1_tags: h1_tags
166
+ },
167
+ images: {
168
+ total_images: images.length,
169
+ missing_alt: missing_alt
170
+ },
171
+ technical: {
172
+ is_https: is_https
173
+ },
174
+ performance: {
175
+ response_time_ms: response_time_ms,
176
+ status_code: status_code
177
+ },
178
+ issues: issues
179
+ }
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SeoWebChecker
4
+ module SeoAudit
5
+ VERSION = "1.0.0"
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "seowebchecker_seoaudit/version"
4
+ require_relative "seowebchecker_seoaudit/auditor"
5
+
6
+ module SeoWebChecker
7
+ module SeoAudit
8
+ def self.audit(url, options = {})
9
+ Auditor.new(**options).audit(url)
10
+ end
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seowebchecker-seoaudit-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - SEOWebChecker Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Instant website SEO auditing, on-page optimization diagnostics, meta
14
+ tag validation, and Core Web Vitals checks.
15
+ email:
16
+ - support@seowebchecker.com
17
+ executables:
18
+ - seowebchecker-audit
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - bin/seowebchecker-audit
25
+ - lib/seowebchecker_seoaudit.rb
26
+ - lib/seowebchecker_seoaudit/auditor.rb
27
+ - lib/seowebchecker_seoaudit/version.rb
28
+ homepage: https://seowebchecker.com
29
+ licenses:
30
+ - MIT
31
+ metadata:
32
+ homepage_uri: https://seowebchecker.com
33
+ source_code_uri: https://github.com/jaiganesh6999/seowebchecker-seoaudit-sdk
34
+ changelog_uri: https://github.com/jaiganesh6999/seowebchecker-seoaudit-sdk/releases
35
+ bug_tracker_uri: https://github.com/jaiganesh6999/seowebchecker-seoaudit-sdk/issues
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 2.7.0
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.4.20
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Lightweight open-source client SDK and CLI tool for full website SEO audits
55
+ by SEOWebChecker.
56
+ test_files: []