sxg_checker 0.1.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36798f6fd5461309de602122e726814a47c484bcf798260796f2b74726d158d4
4
- data.tar.gz: 6ececedef77191f20b4b15d9b32faf84837af6e8f3c03ab87f87cfde1a6d2229
3
+ metadata.gz: a0e170be7a494ed2f4c5d03ce592d49360d51cdaa4bcf487bf2fed7c7e3c47cd
4
+ data.tar.gz: 948f14c9a90571044f5c873b5686cf3eac582529830a6274e6d49391fc00129b
5
5
  SHA512:
6
- metadata.gz: f33f3769f4f2a2671d10434800e0b6d247d7088f98535ab8bda5175f78ab3f1a411f3669f32a95a490254fffb835b791d49691311d3769b51cca607e2fe97e10
7
- data.tar.gz: 7ace8929013bf4681de47150ec6ea9f5e92b535eca33da5aac72293339ddbb64fbcaca477a798ba6af14e344c25193c494182348e1d9f2255dd617cd4d4e8668
6
+ metadata.gz: f2d88e2fb7c90d1cb5397bc51a1561f458d5370a1ebb6e3147d2ea1a9a0d8beeb10801e70be87822ba63c2170da8d0ff84d0e66499ea586a908d8e3defa3fee2
7
+ data.tar.gz: a4d2bcb7468d44d929882662e8f1e773f260908ffbc626bb9b1e05e30e593f7fb5e7b4bc5f837f3bf66a7d976d69499194e0a4c3539eaeec481020bcbf53ef0d
data/README.md CHANGED
@@ -2,11 +2,15 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/sxg_checker.svg)](https://badge.fury.io/rb/sxg_checker)
4
4
 
5
- A library and command-line tool for checking the Google SXG cache for the presence of a document and its subresources.
5
+ A library and command-line tool for checking the Google Signed Exchanges (SXG) cache for the presence of a document and its subresources.
6
6
 
7
- It verifies if your web page and its resources are properly available in Google's Signed Exchanges (SXG) cache,
7
+ It verifies if your web page and its resources are properly available in Google's SXG cache,
8
8
  helping you troubleshoot SXG implementation issues with detailed status reporting for each resource.
9
9
 
10
+ SXG enables websites to optimize Largest Contentful Paint (LCP) by allowing prefetching directly from the Google search results page.
11
+ For a step-by-step guide, see my [SXG tutorial](https://www.pawelpokrywka.com/p/how-i-took-lcp-down-under-350ms).
12
+ This tool is also referenced in the section on [monitoring and measuring SXG](https://www.pawelpokrywka.com/p/measuring-signed-exchanges-impact).
13
+
10
14
  ## Status indicators
11
15
 
12
16
  | Symbol | Status | Description |
@@ -51,14 +55,13 @@ gem contents sxg_checker | grep sxg-checker
51
55
  To check a URL in Google's SXG cache:
52
56
 
53
57
  ```shell
54
- sxg-checker https://www.yourwebsite.com/your-page
58
+ sxg-checker validate https://www.yourwebsite.com/your-page
55
59
  ```
56
60
 
57
- If you installed the `dump-signedexchange` binary somewhere other than `~/go/bin/dump-signedexchange`, specify the
58
- path in the `DSXG_PATH` environment variable:
61
+ To get full usage instructions, run:
59
62
 
60
63
  ```shell
61
- DSXG_PATH=/usr/local/bin/dump-signedexchange sxg-checker https://www.yourwebsite.com/your-page
64
+ sxg-checker --help
62
65
  ```
63
66
 
64
67
  ### In Ruby applications
@@ -69,16 +72,23 @@ You can also use SXG Checker as a library in your Ruby applications:
69
72
  require 'sxg_checker'
70
73
 
71
74
  checker = SxgChecker::Checker.new(tool: '/usr/local/bin/dump-signedexchange') # The `tool` parameter is optional
72
- result = checker.call(url)
75
+ document = checker.validate(url)
73
76
 
74
77
  # Access the results
75
- puts result.url # The URL of the cached document
76
- puts result.status # The status symbol (:ok, :missing, etc.)
78
+ puts document.url # The URL of the cached document
79
+ puts document.status # The status symbol (:ok, :missing, etc.)
77
80
 
78
81
  # Iterate through subresources
79
- result.subresources.each do |subresource|
82
+ document.subresources.each do |subresource|
80
83
  puts "#{subresource.url}: #{subresource.status}"
81
84
  end
85
+
86
+ # If you want to skip validating subresources
87
+ result = checker.validate(url, subresources: false)
88
+
89
+ # If you want to only warm the SXG cache for a given URL
90
+ checker.warm_cache(url)
91
+
82
92
  ```
83
93
 
84
94
  ## Development
data/exe/sxg-checker CHANGED
@@ -4,4 +4,5 @@ require "sxg_checker"
4
4
 
5
5
  exe_name = File.basename($0)
6
6
  cli = SxgChecker::Cli.new(exe_name)
7
- cli.call(ARGV[0])
7
+ code = cli.call(ARGV)
8
+ exit code
data/lib/basic_loader.rb CHANGED
@@ -5,5 +5,5 @@
5
5
  require "sxg_checker/checker"
6
6
  require "sxg_checker/cli"
7
7
  require "sxg_checker/document"
8
- require "sxg_checker/formatter"
8
+ require "sxg_checker/printer"
9
9
  require "sxg_checker/subresource"
@@ -4,9 +4,13 @@ require "parallel"
4
4
 
5
5
  module SxgChecker
6
6
  class Checker
7
- def call(document_url)
8
- raise ToolNotFound.new("executable not found: #{tool}") unless File.executable?(tool)
7
+ def warm_cache(document_url)
8
+ url = cacheify_document_url(document_url)
9
+ fetch_sxg(url)
10
+ nil
11
+ end
9
12
 
13
+ def validate(document_url, subresources: true)
10
14
  url = cacheify_document_url(document_url)
11
15
  response = fetch_sxg(url)
12
16
  return Document.new(url, :missing) unless response
@@ -20,6 +24,8 @@ module SxgChecker
20
24
  fresh_integrity = extract_integrity(sxg.fetch("ResponseHeaders").fetch("Link", [""]))
21
25
  return Document.new(url, :links_mismatch) if fresh_cached.keys.sort != fresh_integrity.keys.sort
22
26
 
27
+ return Document.new(url, :ok) unless subresources
28
+
23
29
  cached_integrity = fresh_integrity.map do |fresh, integrity|
24
30
  cached = fresh_cached.fetch(fresh)
25
31
  [cached, integrity]
@@ -89,27 +95,30 @@ module SxgChecker
89
95
  end
90
96
 
91
97
  def parse_sxg_file(path)
98
+ raise ToolNotFound.new("executable not found: #{tool}") unless tool_available?
99
+
92
100
  command = "#{tool} -json -verify -i #{path}"
93
101
  result = `#{command} 2> /dev/null`
94
102
  return nil if result.empty?
95
103
  JSON.parse(result)
96
104
  end
97
105
 
106
+ def tool_available?
107
+ @tool_available ||= File.executable?(tool)
108
+ end
109
+
98
110
  def cacheify_document_url(url)
99
111
  uri = URI.parse(url)
100
- raise InvalidUrl.new("invalid URL") unless uri.scheme == "https" && uri.host && uri.fragment.nil?
112
+ raise InvalidUrl.new("invalid URL: #{url}") unless uri.scheme == "https" && uri.host && uri.fragment.nil?
101
113
 
102
- host = uri.host.to_s.tr(".", "-")
103
- "https://#{host}.webpkgcache.com/doc/-/s/#{url.sub("https://", "")}"
114
+ "https://#{uri.host.tr(".", "-")}.webpkgcache.com/doc/-/s/#{uri.host}#{uri.request_uri}"
104
115
  rescue URI::InvalidURIError
105
116
  raise InvalidUrl.new("can't parse URL")
106
117
  end
107
118
 
108
119
  attr_reader :tool, :mapper
109
120
 
110
- def initialize(mapper: Parallel.method(:map),
111
- default_tool: "#{ENV["HOME"]}/go/bin/dump-signedexchange",
112
- tool: ENV["DSXG_PATH"] || default_tool)
121
+ def initialize(mapper: Parallel.method(:map), tool: DUMP_BINARY)
113
122
  @mapper = mapper
114
123
  @default_tool = default_tool
115
124
  @tool = tool
@@ -1,49 +1,136 @@
1
+ require "optparse"
2
+
1
3
  module SxgChecker
2
4
  class Cli
3
- def call(url)
4
- return show_usage if url.nil? || url == "-h" || url == "--help"
5
+ def call(argv)
6
+ argv = argv.dup # argv will be changed
7
+ options = {
8
+ subresources: true,
9
+ errors_only: false,
10
+ dump_binary: DUMP_BINARY
11
+ }
12
+
13
+ parser = OptionParser.new do |opts|
14
+ opts.on("-h", "--help") do
15
+ show_usage
16
+ return 0
17
+ end
18
+
19
+ opts.on("-v", "--version") do
20
+ puts VERSION
21
+ return 0
22
+ end
23
+
24
+ opts.on("-n", "--no-subresources") do
25
+ options[:subresources] = false
26
+ end
27
+
28
+ opts.on("-e", "--errors-only") do
29
+ options[:errors_only] = true
30
+ end
5
31
 
6
- document = checker.call(url)
7
- resources = [document] + document.subresources.sort
8
- resources.each do |resource|
9
- puts formatter.resource(resource)
32
+ opts.on("-d", "--dump-binary=PATH", String) do |path|
33
+ options[:dump_binary] = path
34
+ end
35
+ end
36
+
37
+ if argv.empty?
38
+ print_error "command required, use -h for help"
39
+ return 1
40
+ end
41
+
42
+ begin
43
+ # Parse options, leaving the command and URLs in argv
44
+ parser.parse!(argv)
45
+ rescue OptionParser::InvalidOption => e
46
+ print_error e.message
47
+ return 1
48
+ end
49
+
50
+ command = argv.shift
51
+ urls = argv
52
+
53
+ checker = SxgChecker::Checker.new(tool: options[:dump_binary])
54
+
55
+ case command
56
+ when "warm"
57
+ if urls.empty?
58
+ print_error "no URLs provided for warming"
59
+ return 1
60
+ end
61
+
62
+ urls.each do |url|
63
+ checker.warm_cache(url)
64
+ end
65
+ 0
66
+ when "validate"
67
+ if urls.empty?
68
+ print_error "no URLs provided for validation"
69
+ return 1
70
+ end
71
+
72
+ urls.each do |url|
73
+ document = checker.validate(url, subresources: options[:subresources])
74
+ printer.call(document) unless document.ok? && options[:errors_only]
75
+ end
76
+ 0
77
+ else
78
+ print_error "unknown command '#{command}'"
79
+ 1
10
80
  end
11
81
  rescue Error => e
12
- puts "#{exe_name}: error: #{e.message}"
13
- exit 1
82
+ print_error e.message
83
+ 1
14
84
  end
15
85
 
16
86
  private
17
87
 
88
+ def print_error(message)
89
+ puts "#{exe_name}: error: #{message}"
90
+ end
91
+
18
92
  def show_usage
19
- puts "SXG Checker v#{VERSION}"
20
- puts " Checks the Google SXG cache for the presence of a document and its subresources."
93
+ puts "SXG Checker #{VERSION}"
94
+ puts " Queries the Google SXG cache for the presence of documents and their subresources."
21
95
  puts
22
96
  puts "Usage:"
23
- puts " #{exe_name} <URL>"
97
+ puts " #{exe_name} [global options]"
98
+ puts " #{exe_name} warm <URLs>"
99
+ puts " #{exe_name} validate [validate options] <URLs>"
24
100
  puts
25
- puts "Example:"
26
- puts " #{exe_name} https://www.yourwebsite.com/your-page"
101
+ puts "Global options:"
102
+ puts " -h, --help Show this help message and exit"
103
+ puts " -v, --version Show version information and exit"
27
104
  puts
28
- puts "Environment variables:"
29
- puts " DSXG_PATH - path to dump-signedexchange binary (default: #{checker.default_tool})"
105
+ puts "Commands:"
106
+ puts " warm Warm the cache for specified URLs"
107
+ puts " validate Validate SXG documents and their subresources"
108
+ puts
109
+ puts "Validate options:"
110
+ puts " -n, --no-subresources Skip validating subresources (validate documents only)"
111
+ puts " -e, --errors-only Display only errors (skip successfully validated URLs)"
112
+ puts " -d, --dump-binary=PATH Path to dump-signedexchange binary"
113
+ puts " Default: #{DUMP_BINARY}"
30
114
  puts
31
115
  show_statuses
116
+ puts
117
+ puts "Examples:"
118
+ puts " #{exe_name} warm https://example.com/page1"
119
+ puts " #{exe_name} validate -n -e https://example.com/page1 https://another.com"
32
120
  end
33
121
 
34
122
  def show_statuses
35
- puts "Possible statuses:"
36
- formatter.icons.map do |status, icon|
37
- puts " #{icon} #{status.to_s.tr("_", " ")}"
123
+ puts "Validation status indicators:"
124
+ printer.icons.map do |status, icon|
125
+ puts " #{icon} #{status.to_s.tr("_", " ").capitalize}"
38
126
  end
39
127
  end
40
128
 
41
- attr_reader :exe_name, :formatter, :checker
129
+ attr_reader :exe_name, :printer, :default_dump_binary
42
130
 
43
131
  def initialize(exe_name)
44
132
  @exe_name = exe_name
45
- @formatter = SxgChecker::Formatter.new
46
- @checker = SxgChecker::Checker.new
133
+ @printer = SxgChecker::Printer.new
47
134
  end
48
135
  end
49
136
  end
@@ -2,6 +2,10 @@ module SxgChecker
2
2
  class Document
3
3
  attr_reader :url, :status, :subresources
4
4
 
5
+ def ok?
6
+ status == :ok && subresources.all?(&:ok?)
7
+ end
8
+
5
9
  private
6
10
 
7
11
  def initialize(url, status, subresources = [])
@@ -1,14 +1,20 @@
1
1
  module SxgChecker
2
- class Formatter
3
- def resource(resource)
4
- icon = icons.fetch(resource.status)
5
- "#{icon} #{resource.url}"
2
+ class Printer
3
+ def call(document)
4
+ puts "#{icon(document)} #{document.url}"
5
+ document.subresources.sort.each do |subresource|
6
+ puts "#{icon(subresource)} #{subresource.url}"
7
+ end
6
8
  end
7
9
 
8
10
  attr_reader :icons
9
11
 
10
12
  private
11
13
 
14
+ def icon(resource)
15
+ icons.fetch(resource.status)
16
+ end
17
+
12
18
  def initialize(icons = {
13
19
  ok: "✓",
14
20
  missing: "?",
@@ -12,6 +12,10 @@ module SxgChecker
12
12
  fresh_url <=> other.fresh_url
13
13
  end
14
14
 
15
+ def ok?
16
+ status == :ok
17
+ end
18
+
15
19
  private
16
20
 
17
21
  def initialize(url, status)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SxgChecker
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/sxg_checker.rb CHANGED
@@ -16,4 +16,6 @@ module SxgChecker
16
16
  Error = Class.new(StandardError)
17
17
  InvalidUrl = Class.new(Error)
18
18
  ToolNotFound = Class.new(Error)
19
+
20
+ DUMP_BINARY = "#{ENV["HOME"]}/go/bin/dump-signedexchange"
19
21
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sxg_checker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paweł Pokrywka
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-05-08 00:00:00.000000000 Z
11
+ date: 2025-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http
@@ -59,7 +59,7 @@ files:
59
59
  - lib/sxg_checker/checker.rb
60
60
  - lib/sxg_checker/cli.rb
61
61
  - lib/sxg_checker/document.rb
62
- - lib/sxg_checker/formatter.rb
62
+ - lib/sxg_checker/printer.rb
63
63
  - lib/sxg_checker/subresource.rb
64
64
  - lib/sxg_checker/version.rb
65
65
  homepage: https://github.com/pepawel/sxg_checker