axe-cuprite 0.2.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: 91d61bb330869ef24f40d7b9c09859daf23212f99dd8b6c14506238d48cfe9f2
4
- data.tar.gz: 0d023ead7e8245bf03d428ef4912c093a32888c7528d0e95307ca3036a77db2b
3
+ metadata.gz: e56b57fadf23c8ab623bd1a3005d94ce158832160a9ec3bfd1591b070312d953
4
+ data.tar.gz: 2cdad2a18469b669266938df7476040d702b53419bb8a80076e918c282bf218e
5
5
  SHA512:
6
- metadata.gz: b41901a481d94378f0945a89cc4e7e4d6c3f198d45e06737d910841657aaaba6b4f8ccb32538cfb7af45607ea25049787e8a47b85e6e63a45845af7744a1033c
7
- data.tar.gz: 906bb0a2bc25c10140185a9385192c1f629ef3803447cf31862f96e0c9f9d41589da8cb317b8220c6d3ffa37390eb8fe33f1f72729e243c8034104df12e4ae6a
6
+ metadata.gz: 2262ff93c4a88cda10c54d650b74f9353d8d1ae98f0f0a4df82b1483cc755e6347e23be923dedee4fffa726de3229ae4f4e9a00d18f704750f21513038d2515e
7
+ data.tar.gz: 8f001777544278d53266271b66c9d93bb32e0ed2d9e8969fa2413d69342649028be46c78c40706ec526e863c2fa13106a451856b7dfa7db8d23e0852b3367a9f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.0] - 2026-06-16
11
+
12
+ First stable release. No functional changes since `0.2.0` — this tags the API as
13
+ stable under [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The
14
+ public API is the documented surface of `AxeCuprite`, `AxeCuprite::Configuration`,
15
+ `AxeCuprite::Runner`, the result wrappers (`Results`/`Violation`/`Node`/
16
+ `ContrastData`), the error classes, and the RSpec matcher DSL; classes such as
17
+ `AxeCuprite::Injector` and `AxeCuprite::DeepFreeze` are internal and not covered
18
+ by the stability guarantee.
19
+
20
+ ### Fixed
21
+ - The packaged gem no longer ships the dev-only vendoring rake tasks
22
+ (`lib/axe/cuprite/tasks/*.rake` — `axe:update` / `axe:verify` / `axe:version`).
23
+ These operate on the gem's read-only, version-pinned `vendor/` directory and
24
+ `axe:verify` couldn't even run from an installed gem (its recorded checksum
25
+ isn't shipped), so they were broken for consumers. They remain available in a
26
+ repo checkout for development
27
+ ([#32](https://github.com/Guided-Rails/axe-cuprite/issues/32)).
28
+
29
+ ### Changed
30
+ - Repositioned as **Cuprite-only**: the README, gemspec `description`, and
31
+ `AGENTS.md` no longer advertise support for "any real-browser Capybara driver."
32
+ Cuprite is the only supported and tested driver; the driver-neutral JS approach
33
+ is described as the implementation detail that makes the gem work on Cuprite
34
+ (and avoid Selenium internals), not a promise of broad driver support. The
35
+ best-effort non-Ferrum fallback in `Injector#evaluate_axe` is kept but is now
36
+ documented as unsupported/untested
37
+ ([#31](https://github.com/Guided-Rails/axe-cuprite/issues/31)).
38
+ - Documented that `AxeCuprite::Results` (and its nested wrappers) takes ownership
39
+ of the hash passed in and deep-freezes it **in place** — so a caller
40
+ constructing `Results.new(hash)` shouldn't pass or reuse a hash they intend to
41
+ mutate afterward. No behavior change; clarifies the existing read-only contract
42
+ in the `Results` doc comment and the README runner section
43
+ ([#33](https://github.com/Guided-Rails/axe-cuprite/issues/33)).
44
+
45
+ ### Added
46
+ - A unit test pinning the best-effort non-Ferrum fallback in
47
+ `Injector#evaluate_axe`: with no Ferrum page it routes through
48
+ `evaluate_async_script` under the explicit timeout rather than
49
+ `Capybara.default_max_wait_time`, so the path can't silently break
50
+ ([#31](https://github.com/Guided-Rails/axe-cuprite/issues/31)).
51
+
10
52
  ## [0.2.0] - 2026-06-10
11
53
 
12
54
  ### Added
@@ -97,3 +139,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
97
139
  auto-inject toggle, and report-only mode.
98
140
  - `rake axe:update[VERSION]` to refresh the vendored axe-core engine and bump
99
141
  the `AXE_CORE_VERSION` constant.
142
+
143
+ [Unreleased]: https://github.com/Guided-Rails/axe-cuprite/compare/v1.0.0...HEAD
144
+ [1.0.0]: https://github.com/Guided-Rails/axe-cuprite/compare/v0.2.0...v1.0.0
145
+ [0.2.0]: https://github.com/Guided-Rails/axe-cuprite/compare/v0.1.1...v0.2.0
146
+ [0.1.1]: https://github.com/Guided-Rails/axe-cuprite/compare/v0.1.0...v0.1.1
147
+ [0.1.0]: https://github.com/Guided-Rails/axe-cuprite/releases/tag/v0.1.0
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # axe-cuprite
2
2
 
3
+ [![Gem Version](https://img.shields.io/gem/v/axe-cuprite)](https://rubygems.org/gems/axe-cuprite)
4
+ [![CI](https://github.com/Guided-Rails/axe-cuprite/actions/workflows/ci.yml/badge.svg)](https://github.com/Guided-Rails/axe-cuprite/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
6
+
3
7
  Run the [axe-core](https://github.com/dequelabs/axe-core) accessibility engine
4
8
  against pages in your **Capybara system/feature tests driven by
5
9
  [Cuprite](https://github.com/rubycdp/cuprite)** (the CDP/Ferrum headless-Chrome
@@ -23,13 +27,24 @@ they break.
23
27
  just a JavaScript file — a driver-agnostic engine — so we drive it exclusively
24
28
  through Capybara's **driver-neutral** JavaScript API (`execute_script`,
25
29
  `evaluate_async_script`), which Cuprite fully implements. That single decision is
26
- what makes this gem work where the official one doesn't. As a bonus it stays
27
- driver-agnostic (it works on any real-browser Capybara driver), but **Cuprite is
28
- the primary, must-pass target.**
30
+ what makes this gem work where the official one doesn't.
31
+
32
+ **Cuprite is the only supported and tested driver.** Driving axe through the
33
+ driver-neutral JS API is what lets the gem avoid Selenium internals — not a promise
34
+ of broad driver support. Other real-browser drivers may happen to work (a
35
+ best-effort fallback exists), but they are unsupported and untested.
29
36
 
30
37
  There is no runtime dependency on Selenium, Cuprite, or Ferrum — the only runtime
31
38
  dependency is Capybara. You bring your own driver.
32
39
 
40
+ ## Requirements
41
+
42
+ - **Ruby** >= 3.0
43
+ - **Capybara** >= 3.0, < 4.0 (the only runtime dependency)
44
+ - **[Cuprite](https://github.com/rubycdp/cuprite)** as the Capybara driver — the
45
+ only supported and tested driver
46
+ - **Chrome or Chromium**, which Cuprite drives over CDP
47
+
33
48
  ## Installation
34
49
 
35
50
  ```ruby
@@ -146,6 +161,13 @@ on top of your configured defaults. Only `violations` and `incomplete` are
146
161
  carried back across the CDP boundary — the full results object (with
147
162
  `passes`/`inapplicable`) can be huge.
148
163
 
164
+ `AxeCuprite::Results` is read-only: it **takes ownership of the hash it's given
165
+ and deep-freezes it in place** (this backs the read-only `raw`/`to_h` contract).
166
+ You normally get one straight from `Runner#run`, so this is transparent — but if
167
+ you construct `Results.new(hash)` yourself, don't pass or keep a reference to a
168
+ hash you intend to mutate afterward, or you'll hit a `FrozenError`. Dup it first
169
+ if you need a mutable copy.
170
+
149
171
  ## Configuration
150
172
 
151
173
  ```ruby
@@ -197,10 +219,11 @@ page routinely exceeds.
197
219
  axe-cuprite avoids this trap: on Cuprite it calls Ferrum's
198
220
  `page.evaluate_async(script, explicit_wait, *args)` **directly**, with its own
199
221
  timeout (default **30s**, configurable) that is completely **decoupled from
200
- `default_max_wait_time`**. On non-Ferrum drivers it falls back to
201
- `evaluate_async_script` under a temporarily-raised wait time. If axe still
202
- doesn't finish, you get a clear `AxeCuprite::TimeoutError` telling you to raise
203
- the timeout or scope the run with `.within`.
222
+ `default_max_wait_time`**. (On a non-Ferrum driver there is a best-effort,
223
+ untested fallback to `evaluate_async_script` under a temporarily-raised wait time
224
+ but Cuprite is the only supported driver.) If axe still doesn't finish, you get a
225
+ clear `AxeCuprite::TimeoutError` telling you to raise the timeout or scope the run
226
+ with `.within`.
204
227
 
205
228
  Tune it globally (`c.timeout = 60`) or per assertion (`.with_timeout(60)`).
206
229
 
@@ -252,6 +275,22 @@ sha512 of the vendored engine is recorded in
252
275
  with `rake axe:verify` (CI does this on every run). Note the bump in
253
276
  `CHANGELOG.md`.
254
277
 
278
+ ## Versioning
279
+
280
+ axe-cuprite follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
281
+ The **public API** covered by that guarantee is the documented surface:
282
+ `AxeCuprite` (module methods), `AxeCuprite::Configuration`, `AxeCuprite::Runner`,
283
+ the result wrappers (`Results` / `Violation` / `Node` / `ContrastData`), the error
284
+ classes, and the RSpec matcher DSL (`be_axe_clean` / `be_accessible` and its
285
+ chainers). Classes like `AxeCuprite::Injector` and `AxeCuprite::DeepFreeze` are
286
+ internal implementation details and may change in any release. Breaking changes to
287
+ the public API only land in a major version, and every release is noted in
288
+ [`CHANGELOG.md`](CHANGELOG.md).
289
+
290
+ Note that the **vendored axe-core version** can change in a minor release; axe may
291
+ report new violations after an engine bump, which can turn a previously-green
292
+ suite red. Such bumps are always called out in the changelog.
293
+
255
294
  ## Licensing
256
295
 
257
296
  - **axe-cuprite's own code** is licensed under the **MIT** license — see
@@ -26,6 +26,13 @@ module AxeCuprite
26
26
  #
27
27
  # The wrappers are read-only: `@raw` is deep-frozen at construction, so `raw`
28
28
  # and `to_h` expose the live underlying hash safely (callers cannot mutate it).
29
+ #
30
+ # Ownership note: `Results` (and the nested `Violation`/`Node`/`ContrastData`
31
+ # wrappers) **take ownership of the hash passed in and deep-freeze it in place**
32
+ # — they do not copy first. For the internal flow this is safe (the payload
33
+ # comes fresh off the CDP boundary), but if you construct `Results.new(hash)`
34
+ # yourself, don't pass — or hold onto — a hash you intend to mutate afterward,
35
+ # or you'll hit a `FrozenError`. Dup it first if you need a mutable copy.
29
36
  class Results
30
37
  attr_reader :raw
31
38
 
@@ -73,11 +73,7 @@ module AxeCuprite
73
73
  # axe accepts a selector string, or {include:, exclude:}. Stringify hash
74
74
  # keys so Ferrum serializes them predictably across CDP.
75
75
  def normalize_context(context)
76
- case context
77
- when nil, String then context
78
- when Hash then deep_stringify(context)
79
- else context
80
- end
76
+ context.is_a?(Hash) ? deep_stringify(context) : context
81
77
  end
82
78
 
83
79
  def deep_stringify(value)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AxeCuprite
4
4
  # Version of the axe-cuprite gem itself.
5
- VERSION = "0.2.0"
5
+ VERSION = "1.0.0"
6
6
 
7
7
  # Version of the axe-core engine vendored under lib/axe/cuprite/vendor/axe.min.js.
8
8
  # Keep this in sync with the vendored file via `rake axe:update`.
data/lib/axe/cuprite.rb CHANGED
@@ -8,8 +8,10 @@ require "axe/cuprite/injector"
8
8
  require "axe/cuprite/runner"
9
9
 
10
10
  # axe-cuprite drives the axe-core accessibility engine through Capybara's
11
- # driver-neutral JavaScript API, so it works with Cuprite (Ferrum/CDP) and any
12
- # other real-browser Capybara driverwithout ever touching Selenium internals.
11
+ # driver-neutral JavaScript API. That approach is what lets it avoid Selenium
12
+ # internals and work on Cuprite (Ferrum/CDP) the only supported and tested
13
+ # driver. Other real-browser drivers may work via a best-effort, untested
14
+ # fallback, but they are unsupported.
13
15
  #
14
16
  # Quick start:
15
17
  #
data/lib/axe-cuprite.rb CHANGED
@@ -1,5 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Convenience entry point so `require "axe-cuprite"` works.
4
- # The canonical require path is "axe/cuprite".
5
3
  require "axe/cuprite"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: axe-cuprite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdullah Hashim
@@ -29,165 +29,13 @@ dependencies:
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: '4.0'
32
- - !ruby/object:Gem::Dependency
33
- name: cuprite
34
- requirement: !ruby/object:Gem::Requirement
35
- requirements:
36
- - - "~>"
37
- - !ruby/object:Gem::Version
38
- version: '0.17'
39
- type: :development
40
- prerelease: false
41
- version_requirements: !ruby/object:Gem::Requirement
42
- requirements:
43
- - - "~>"
44
- - !ruby/object:Gem::Version
45
- version: '0.17'
46
- - !ruby/object:Gem::Dependency
47
- name: ferrum
48
- requirement: !ruby/object:Gem::Requirement
49
- requirements:
50
- - - "~>"
51
- - !ruby/object:Gem::Version
52
- version: '0.17'
53
- type: :development
54
- prerelease: false
55
- version_requirements: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - "~>"
58
- - !ruby/object:Gem::Version
59
- version: '0.17'
60
- - !ruby/object:Gem::Dependency
61
- name: puma
62
- requirement: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: '5.0'
67
- - - "<"
68
- - !ruby/object:Gem::Version
69
- version: '9.0'
70
- type: :development
71
- prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '5.0'
77
- - - "<"
78
- - !ruby/object:Gem::Version
79
- version: '9.0'
80
- - !ruby/object:Gem::Dependency
81
- name: rack
82
- requirement: !ruby/object:Gem::Requirement
83
- requirements:
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- version: '2.0'
87
- - - "<"
88
- - !ruby/object:Gem::Version
89
- version: '4.0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '2.0'
97
- - - "<"
98
- - !ruby/object:Gem::Version
99
- version: '4.0'
100
- - !ruby/object:Gem::Dependency
101
- name: rackup
102
- requirement: !ruby/object:Gem::Requirement
103
- requirements:
104
- - - "~>"
105
- - !ruby/object:Gem::Version
106
- version: '2.0'
107
- type: :development
108
- prerelease: false
109
- version_requirements: !ruby/object:Gem::Requirement
110
- requirements:
111
- - - "~>"
112
- - !ruby/object:Gem::Version
113
- version: '2.0'
114
- - !ruby/object:Gem::Dependency
115
- name: rake
116
- requirement: !ruby/object:Gem::Requirement
117
- requirements:
118
- - - "~>"
119
- - !ruby/object:Gem::Version
120
- version: '13.0'
121
- type: :development
122
- prerelease: false
123
- version_requirements: !ruby/object:Gem::Requirement
124
- requirements:
125
- - - "~>"
126
- - !ruby/object:Gem::Version
127
- version: '13.0'
128
- - !ruby/object:Gem::Dependency
129
- name: rspec
130
- requirement: !ruby/object:Gem::Requirement
131
- requirements:
132
- - - "~>"
133
- - !ruby/object:Gem::Version
134
- version: '3.0'
135
- type: :development
136
- prerelease: false
137
- version_requirements: !ruby/object:Gem::Requirement
138
- requirements:
139
- - - "~>"
140
- - !ruby/object:Gem::Version
141
- version: '3.0'
142
- - !ruby/object:Gem::Dependency
143
- name: rubocop
144
- requirement: !ruby/object:Gem::Requirement
145
- requirements:
146
- - - "~>"
147
- - !ruby/object:Gem::Version
148
- version: '1.86'
149
- type: :development
150
- prerelease: false
151
- version_requirements: !ruby/object:Gem::Requirement
152
- requirements:
153
- - - "~>"
154
- - !ruby/object:Gem::Version
155
- version: '1.86'
156
- - !ruby/object:Gem::Dependency
157
- name: rubocop-rake
158
- requirement: !ruby/object:Gem::Requirement
159
- requirements:
160
- - - "~>"
161
- - !ruby/object:Gem::Version
162
- version: '0.7'
163
- type: :development
164
- prerelease: false
165
- version_requirements: !ruby/object:Gem::Requirement
166
- requirements:
167
- - - "~>"
168
- - !ruby/object:Gem::Version
169
- version: '0.7'
170
- - !ruby/object:Gem::Dependency
171
- name: rubocop-rspec
172
- requirement: !ruby/object:Gem::Requirement
173
- requirements:
174
- - - "~>"
175
- - !ruby/object:Gem::Version
176
- version: '3.0'
177
- type: :development
178
- prerelease: false
179
- version_requirements: !ruby/object:Gem::Requirement
180
- requirements:
181
- - - "~>"
182
- - !ruby/object:Gem::Version
183
- version: '3.0'
184
32
  description: |
185
33
  axe-cuprite runs the axe-core accessibility engine against pages rendered in
186
34
  Capybara system/feature tests and exposes the results as RSpec matchers
187
35
  (be_axe_clean / be_accessible). Unlike Deque's official axe-core-capybara gem,
188
36
  it never touches Selenium-specific driver internals: axe is driven entirely
189
- through Capybara's driver-neutral JavaScript API, so it works with Cuprite
190
- and any other real-browser driver.
37
+ through Capybara's driver-neutral JavaScript API, which is what makes it work
38
+ on Cuprite. Cuprite is the only supported and tested driver.
191
39
  email:
192
40
  - abdullah@guidedrails.com
193
41
  executables: []
@@ -207,7 +55,6 @@ files:
207
55
  - lib/axe/cuprite/rspec.rb
208
56
  - lib/axe/cuprite/rspec/matchers.rb
209
57
  - lib/axe/cuprite/runner.rb
210
- - lib/axe/cuprite/tasks/axe.rake
211
58
  - lib/axe/cuprite/vendor/axe-core-LICENSE.txt
212
59
  - lib/axe/cuprite/vendor/axe.min.js
213
60
  - lib/axe/cuprite/version.rb
@@ -217,6 +64,8 @@ licenses:
217
64
  metadata:
218
65
  source_code_uri: https://github.com/Guided-Rails/axe-cuprite
219
66
  changelog_uri: https://github.com/Guided-Rails/axe-cuprite/blob/main/CHANGELOG.md
67
+ bug_tracker_uri: https://github.com/Guided-Rails/axe-cuprite/issues
68
+ documentation_uri: https://github.com/Guided-Rails/axe-cuprite/blob/main/README.md
220
69
  rubygems_mfa_required: 'true'
221
70
  rdoc_options: []
222
71
  require_paths:
@@ -1,169 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
- require "fileutils"
5
- require "json"
6
- require "open-uri"
7
- require "rubygems/package"
8
- require "stringio"
9
- require "zlib"
10
-
11
- namespace :axe do
12
- desc "Refresh the vendored axe-core engine. Usage: rake 'axe:update[4.12.0]' or VERSION=4.12.0 rake axe:update (default: latest)"
13
- task :update, [:version] do |_task, args|
14
- version = args[:version] || ENV["VERSION"] || AxeCupriteVendor.latest_version
15
- AxeCupriteVendor.update!(version)
16
- end
17
-
18
- desc "Print the currently vendored axe-core version"
19
- task :version do
20
- puts AxeCupriteVendor.vendored_version
21
- end
22
-
23
- desc "Verify the vendored axe.min.js against its recorded sha512 checksum"
24
- task :verify do
25
- AxeCupriteVendor.verify_vendored!
26
- end
27
- end
28
-
29
- # Helpers for vendoring axe-core. Kept in a module so the rake tasks stay thin
30
- # and the logic is easy to read. Never used at runtime — vendoring is a
31
- # development-time step; the engine ships in the gem.
32
- #
33
- # Supply-chain note: the vendored axe.min.js is the JavaScript this gem injects
34
- # into every consumer's browser session, so its integrity is the gem's most
35
- # important supply-chain property. We therefore download the *official npm
36
- # tarball* from registry.npmjs.org (not a CDN re-serving it), verify it against
37
- # the sha512 integrity the registry publishes in its metadata, and abort on any
38
- # mismatch before a single byte is written. The verified content hash of the
39
- # extracted axe.min.js is recorded next to it (axe.min.js.sha512) so reviewers
40
- # and CI can re-check the vendored artifact without re-downloading.
41
- module AxeCupriteVendor
42
- VENDOR_DIR = File.expand_path("../vendor", __dir__)
43
- AXE_JS = File.join(VENDOR_DIR, "axe.min.js")
44
- AXE_SHA512 = File.join(VENDOR_DIR, "axe.min.js.sha512")
45
- AXE_LICENSE = File.join(VENDOR_DIR, "axe-core-LICENSE.txt")
46
- VERSION_FILE = File.expand_path("../version.rb", __dir__)
47
- REGISTRY = "https://registry.npmjs.org/axe-core"
48
-
49
- module_function
50
-
51
- # A semver-ish version string: three dot-separated numbers with an optional
52
- # prerelease suffix. Rejecting anything else keeps the value safe to splice
53
- # into the registry URL and into version.rb, and catches honest typos before
54
- # they corrupt the vendor directory.
55
- VERSION_FORMAT = /\A\d+\.\d+\.\d+(-[\w.]+)?\z/
56
-
57
- def update!(version)
58
- abort "Invalid version: #{version.inspect}" unless version.match?(VERSION_FORMAT)
59
-
60
- FileUtils.mkdir_p(VENDOR_DIR)
61
-
62
- dist = registry_dist(version)
63
- puts "Downloading #{dist.fetch("tarball")} ..."
64
- tarball = download(dist.fetch("tarball"))
65
- verify_tarball!(tarball, dist)
66
-
67
- js, license = extract(tarball, "package/axe.min.js", "package/LICENSE")
68
- verify_banner!(js, version)
69
-
70
- File.write(AXE_JS, js)
71
- File.write(AXE_LICENSE, license)
72
- File.write(AXE_SHA512, "#{Digest::SHA512.hexdigest(js)} axe.min.js\n")
73
- bump_version_constant(version)
74
-
75
- report(version, js)
76
- end
77
-
78
- def latest_version
79
- JSON.parse(download("#{REGISTRY}/latest")).fetch("version")
80
- end
81
-
82
- def vendored_version
83
- File.read(AXE_JS)[/axe v([0-9][0-9.]*)/, 1] || "unknown"
84
- end
85
-
86
- def verify_vendored!
87
- abort "No recorded checksum at #{AXE_SHA512} — run rake axe:update first." unless File.exist?(AXE_SHA512)
88
-
89
- expected = File.read(AXE_SHA512).split.first
90
- actual = Digest::SHA512.hexdigest(File.binread(AXE_JS))
91
- unless actual == expected
92
- abort <<~MSG
93
- MISMATCH: #{AXE_JS} does not match its recorded sha512.
94
- recorded: #{expected}
95
- actual: #{actual}
96
- MSG
97
- end
98
-
99
- puts "OK: axe.min.js matches its recorded sha512."
100
- end
101
-
102
- def registry_dist(version)
103
- puts "Fetching axe-core@#{version} metadata from #{REGISTRY} ..."
104
- JSON.parse(download("#{REGISTRY}/#{version}")).fetch("dist")
105
- end
106
-
107
- # Abort unless the downloaded tarball matches the integrity values the npm
108
- # registry publishes for it (dist.integrity sha512, plus legacy dist.shasum).
109
- def verify_tarball!(tarball, dist)
110
- integrity = dist["integrity"]
111
- abort "Registry metadata has no sha512 integrity for the tarball — refusing to vendor." unless integrity&.start_with?("sha512-")
112
-
113
- actual = "sha512-#{Digest::SHA512.base64digest(tarball)}"
114
- unless actual == integrity
115
- abort <<~MSG
116
- Tarball integrity check FAILED — refusing to vendor.
117
- expected (registry dist.integrity): #{integrity}
118
- actual (downloaded tarball): #{actual}
119
- MSG
120
- end
121
-
122
- shasum = dist["shasum"]
123
- if shasum && Digest::SHA1.hexdigest(tarball) != shasum
124
- abort "Tarball sha1 shasum mismatch (registry says #{shasum}) — refusing to vendor."
125
- end
126
-
127
- puts " tarball integrity verified (#{integrity})"
128
- end
129
-
130
- # Secondary sanity check on the extracted engine itself; fatal on mismatch.
131
- def verify_banner!(engine_js, version)
132
- return if engine_js.match?(/axe v#{Regexp.escape(version)}\b/)
133
-
134
- abort "Extracted axe.min.js banner does not mention v#{version} — refusing to vendor."
135
- end
136
-
137
- def extract(tarball, *paths)
138
- found = {}
139
- Zlib::GzipReader.wrap(StringIO.new(tarball)) do |gz|
140
- Gem::Package::TarReader.new(gz) do |tar|
141
- tar.each { |entry| found[entry.full_name] = entry.read if paths.include?(entry.full_name) }
142
- end
143
- end
144
- missing = paths - found.keys
145
- abort "Tarball is missing expected file(s): #{missing.join(", ")}" unless missing.empty?
146
- found.values_at(*paths)
147
- end
148
-
149
- def download(url)
150
- URI.parse(url).open("rb", &:read)
151
- rescue OpenURI::HTTPError => e
152
- abort "Failed to download #{url}: #{e.message}"
153
- end
154
-
155
- def bump_version_constant(version)
156
- contents = File.read(VERSION_FILE)
157
- updated = contents.sub(/(AXE_CORE_VERSION\s*=\s*)"[^"]*"/, %(\\1"#{version}"))
158
- File.write(VERSION_FILE, updated)
159
- end
160
-
161
- def report(version, engine_js)
162
- puts "Vendored axe-core #{version} (tarball integrity verified):"
163
- puts " #{AXE_JS} (#{engine_js.bytesize} bytes)"
164
- puts " #{AXE_SHA512}"
165
- puts " #{AXE_LICENSE}"
166
- puts " updated AXE_CORE_VERSION in #{VERSION_FILE}"
167
- puts "Remember to note the version bump in CHANGELOG.md."
168
- end
169
- end