axe-cuprite 0.1.1 → 0.2.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: 1005023d8e3178edd24de983e954208d297ee0ca720f33ba822f14c33861b17f
4
- data.tar.gz: 5b0b13f67c92e8b89d1d4b4f2f9b4957ae71690d60eeb006edd2e9239bd89dd7
3
+ metadata.gz: 91d61bb330869ef24f40d7b9c09859daf23212f99dd8b6c14506238d48cfe9f2
4
+ data.tar.gz: 0d023ead7e8245bf03d428ef4912c093a32888c7528d0e95307ca3036a77db2b
5
5
  SHA512:
6
- metadata.gz: 439317e53b00d1f6a4f4c275f02b5caa9c6d0add981079b809a3363c937fab3431226db540f6163ad908a47b0869782db0b0bfd98f5b229bb121b45b9733acb6
7
- data.tar.gz: 9ade47a886507d6349a680abd9446fc5191048172b1a40f660a694dc93afa0841528d73a5eb4e281d30960fa4fad4b72e87d359a969c3e15274f9213c76c9587
6
+ metadata.gz: b41901a481d94378f0945a89cc4e7e4d6c3f198d45e06737d910841657aaaba6b4f8ccb32538cfb7af45607ea25049787e8a47b85e6e63a45845af7744a1033c
7
+ data.tar.gz: 906bb0a2bc25c10140185a9385192c1f629ef3803447cf31862f96e0c9f9d41589da8cb317b8220c6d3ffa37390eb8fe33f1f72729e243c8034104df12e4ae6a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,65 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-06-10
11
+
12
+ ### Added
13
+ - `config.include_html` (default `true`) — set to `false` to suppress the
14
+ truncated outer-HTML snippets that failure messages and `report_only` logs
15
+ print for each offending element. Useful for suites that render sensitive data
16
+ (staging-backed tests, seeded PII), keeping page content out of CI logs while
17
+ still reporting rule id, selector, and check message. Documented the snippet
18
+ behavior and `logger` guidance in the README
19
+ ([#14](https://github.com/Guided-Rails/axe-cuprite/issues/14)).
20
+
21
+ ### Fixed
22
+ - The result wrappers (`Results`/`Violation`/`Node`/`ContrastData`) now honor
23
+ their documented read-only contract: `@raw` is deep-frozen at construction, so
24
+ `raw` and `to_h` can safely expose the live underlying hash without a caller
25
+ being able to mutate the wrapper's internal state (which, via memoization,
26
+ could previously desync `violations`/`incomplete` from `raw`)
27
+ ([#17](https://github.com/Guided-Rails/axe-cuprite/issues/17)).
28
+ - `Injector#inject_source!` no longer hides the real cause of an injection
29
+ failure behind a blanket Content-Security-Policy message. When both the
30
+ primary `execute_script` path and the `add_script_tag` fallback fail, the
31
+ exceptions they raised are now captured and appended to the `InjectionError`
32
+ message (`Underlying errors: execute_script: ...; add_script_tag: ...`), so a
33
+ dead browser, dead CDP session, or misconfigured driver is no longer
34
+ misattributed to CSP ([#15](https://github.com/Guided-Rails/axe-cuprite/issues/15)).
35
+ - `Injector#timeout_error?` no longer reclassifies arbitrary failures as
36
+ `AxeCuprite::TimeoutError` just because the error message mentions a timeout.
37
+ A genuine page-side JavaScript error (e.g. a `Ferrum::JavaScriptError` from axe
38
+ or the app whose text happens to contain "timeout") now propagates untouched
39
+ instead of being rewritten with misleading "increase the timeout / scope the
40
+ run" guidance. Classification is driven by error class (Ferrum's
41
+ timeout/script-timeout classes), with a narrow class-gated message check only
42
+ for Ferrum's async-evaluation "timed out promise" case; this also removes a
43
+ dead code branch that could never affect the result
44
+ ([#16](https://github.com/Guided-Rails/axe-cuprite/issues/16)).
45
+
46
+ ### Security
47
+ - `rake axe:update` now vendors axe-core from the official npm registry tarball
48
+ (`registry.npmjs.org`) instead of the unpkg CDN, verifies the tarball against
49
+ the registry-published sha512 `dist.integrity` (and legacy `dist.shasum`)
50
+ **before writing anything**, and treats a banner/version mismatch as fatal
51
+ instead of a warning ([#11](https://github.com/Guided-Rails/axe-cuprite/issues/11)).
52
+ - The sha512 of the vendored `axe.min.js` is now recorded in
53
+ `lib/axe/cuprite/vendor/axe.min.js.sha512`; a new `rake axe:verify` task
54
+ re-checks the vendored engine against it, and CI runs it on every build.
55
+ - Hardened the CI workflow: added a least-privilege `permissions: contents: read`
56
+ block (the `GITHUB_TOKEN` previously inherited the repo default, potentially
57
+ write-all) and pinned `actions/checkout` and `ruby/setup-ruby` to full commit
58
+ SHAs instead of mutable major-version tags. Added a Dependabot config
59
+ (`.github/dependabot.yml`) for the `github-actions` and `bundler` ecosystems so
60
+ the pinned SHAs and gem dependencies get automated update PRs
61
+ ([#12](https://github.com/Guided-Rails/axe-cuprite/issues/12)).
62
+ - `rake axe:update[VERSION]` now validates the `VERSION` argument against a
63
+ semver-ish pattern **before** any network call or file write. The value was
64
+ previously spliced unvalidated into the registry/download URL and into
65
+ `version.rb`, so a crafted string (e.g. `4.12.0/../other-pkg`, or one
66
+ containing a quote) could vendor a different package or break out of the
67
+ version string literal ([#13](https://github.com/Guided-Rails/axe-cuprite/issues/13)).
68
+
10
69
  ## [0.1.1] - 2026-06-09
11
70
 
12
71
  ### Added
data/README.md CHANGED
@@ -156,6 +156,7 @@ AxeCuprite.configure do |c|
156
156
  c.skip_rules = [:region] # globally disabled rules
157
157
  c.auto_inject = true # (re)inject axe on demand inside #run
158
158
  c.report_only = false # log violations instead of failing (see below)
159
+ c.include_html = true # include element HTML snippets in output (see below)
159
160
  c.logger = Logger.new($stdout)
160
161
  end
161
162
  ```
@@ -167,6 +168,22 @@ This eases incremental adoption on an existing app — you can see what axe find
167
168
  without turning the suite red. Negated assertions (`expect(page).not_to
168
169
  be_axe_clean`) ignore this flag.
169
170
 
171
+ ### Sensitive page content in output (`include_html`)
172
+
173
+ Both failure messages and `report_only` logs include a **truncated outer-HTML
174
+ snippet** (capped at 200 chars) of each offending element, so you can see *what*
175
+ failed at a glance. On suites that render real data — staging-backed tests,
176
+ seeded PII — those snippets can carry page content into CI logs or log
177
+ aggregation. This is normal for a testing tool, but if it matters to you:
178
+
179
+ - Point `c.logger` somewhere appropriate (a redacted sink, a dropped stream)
180
+ rather than `$stdout`, so `report_only` output doesn't fan out to aggregation.
181
+ - Set `c.include_html = false` to drop the HTML snippets entirely. Output then
182
+ carries only the rule id, impact, help URL, element **selector**, and the axe
183
+ check message (for color-contrast, the fg/bg colors and ratio) — enough to
184
+ locate and fix a violation without echoing page content. Note the selector
185
+ itself can still reflect ids/classes from your markup.
186
+
170
187
  ## Caveats & engineering notes
171
188
 
172
189
  ### Timeout (decoupled from `default_max_wait_time`)
@@ -223,10 +240,17 @@ To refresh it:
223
240
  rake 'axe:update[4.12.0]' # pin a version
224
241
  rake axe:update # or grab the latest from npm
225
242
  rake axe:version # print the currently vendored version
243
+ rake axe:verify # check axe.min.js against its recorded sha512
226
244
  ```
227
245
 
228
- `axe:update` downloads `axe.min.js` and its `LICENSE` from unpkg and bumps the
229
- `AXE_CORE_VERSION` constant. Note the bump in `CHANGELOG.md`.
246
+ `axe:update` downloads the official `axe-core` tarball from
247
+ **registry.npmjs.org**, verifies it against the sha512 integrity the registry
248
+ publishes (aborting on any mismatch before writing a byte), extracts
249
+ `axe.min.js` and its `LICENSE`, and bumps the `AXE_CORE_VERSION` constant. The
250
+ sha512 of the vendored engine is recorded in
251
+ `lib/axe/cuprite/vendor/axe.min.js.sha512` so it can be re-checked at any time
252
+ with `rake axe:verify` (CI does this on every run). Note the bump in
253
+ `CHANGELOG.md`.
230
254
 
231
255
  ## Licensing
232
256
 
@@ -34,6 +34,13 @@ module AxeCuprite
34
34
  # Logger used for report_only output and warnings.
35
35
  attr_accessor :logger
36
36
 
37
+ # When true (default), failure messages and report_only logs include a
38
+ # truncated outer-HTML snippet of each offending element. Set to false to
39
+ # suppress those snippets (rule id + selector + check message only) on
40
+ # suites that render sensitive data, so page content can't leak into CI
41
+ # logs. See BeAxeClean#format_node.
42
+ attr_accessor :include_html
43
+
37
44
  def initialize
38
45
  @timeout = 30
39
46
  @default_options = {}
@@ -41,6 +48,7 @@ module AxeCuprite
41
48
  @skip_rules = []
42
49
  @auto_inject = true
43
50
  @report_only = false
51
+ @include_html = true
44
52
  @logger = default_logger
45
53
  end
46
54
 
@@ -46,6 +46,17 @@ module AxeCuprite
46
46
  # JS expression that reports whether axe is loaded and runnable.
47
47
  PRESENCE_JS = "typeof window.axe !== 'undefined' && typeof window.axe.run === 'function'"
48
48
 
49
+ # Dedicated timeout classes that mean "the evaluation timed out" regardless
50
+ # of message. Matched by name (not constant) so we keep no hard dependency on
51
+ # ferrum — it's a dev-only dep. Covers Ferrum's own timeouts (the Cuprite
52
+ # fast-path, via page.evaluate_async). A non-Ferrum driver's own script-timeout
53
+ # error propagates raw rather than being wrapped — we don't depend on or test
54
+ # any such driver here, so we don't guess at its class names. See #timeout_error?.
55
+ TIMEOUT_ERROR_CLASS_NAMES = [
56
+ "Ferrum::TimeoutError",
57
+ "Ferrum::ScriptTimeoutError"
58
+ ].freeze
59
+
49
60
  def initialize(page, configuration = AxeCuprite.configuration)
50
61
  @page = page
51
62
  @config = configuration
@@ -73,20 +84,23 @@ module AxeCuprite
73
84
  # to Ferrum's add_script_tag.
74
85
  def inject_source!
75
86
  source = AxeCuprite.axe_source
87
+ errors = {}
76
88
 
77
89
  begin
78
90
  @page.execute_script(source)
79
- rescue StandardError
80
- nil
91
+ rescue StandardError => e
92
+ errors["execute_script"] = e
81
93
  end
82
94
  return true if injected?
83
95
 
84
- try_add_script_tag(source)
96
+ begin
97
+ try_add_script_tag(source)
98
+ rescue StandardError => e
99
+ errors["add_script_tag"] = e
100
+ end
85
101
  return true if injected?
86
102
 
87
- raise InjectionError,
88
- "axe-core did not load after injection. The page may be blocking " \
89
- "script injection (e.g. a strict Content-Security-Policy)."
103
+ raise InjectionError, injection_failure_message(errors)
90
104
  end
91
105
 
92
106
  # Run axe and return a Results object. Injects on demand if needed.
@@ -154,24 +168,47 @@ module AxeCuprite
154
168
  nil
155
169
  end
156
170
 
157
- # Best-effort CSP fallback via Ferrum's add_script_tag(content:).
171
+ # Best-effort CSP fallback via Ferrum's add_script_tag(content:). Returns
172
+ # false when no Ferrum add_script_tag is available (non-Ferrum drivers); lets
173
+ # a genuine injection failure propagate so inject_source! can report it.
158
174
  def try_add_script_tag(source)
159
175
  fpage = ferrum_page
160
176
  return false unless fpage.respond_to?(:add_script_tag)
161
177
 
162
178
  fpage.add_script_tag(content: source)
163
179
  true
164
- rescue StandardError
165
- false
166
180
  end
167
181
 
182
+ # Build the InjectionError message, appending whatever the injection paths
183
+ # actually raised so a non-CSP failure (dead browser, dead CDP session,
184
+ # misconfigured driver) isn't silently blamed on Content-Security-Policy.
185
+ def injection_failure_message(errors)
186
+ message = "axe-core did not load after injection. The page may be blocking " \
187
+ "script injection (e.g. a strict Content-Security-Policy)."
188
+ return message if errors.empty?
189
+
190
+ detail = errors.map { |path, e| "#{path}: #{e.class}: #{e.message}" }.join("; ")
191
+ "#{message}\nUnderlying errors: #{detail}"
192
+ end
193
+
194
+ # Did `evaluate_axe` fail because axe.run genuinely timed out (so the
195
+ # "increase the timeout / scope the run" guidance is right), or did it hit a
196
+ # real page-side error that must propagate untouched?
197
+ #
198
+ # Classification is driven by error CLASS, not message. A message that merely
199
+ # mentions a timeout is deliberately NOT sufficient: a real Ferrum::JavaScriptError
200
+ # from axe or the app whose text happens to contain "timeout" must not be
201
+ # rewritten as an AxeCuprite::TimeoutError with misleading guidance.
202
+ #
203
+ # The one message check is narrow and class-gated: Ferrum reports its own
204
+ # async-evaluation timeout (page.evaluate_async) as a generic JavaScriptError
205
+ # carrying a "timed out promise" message, so for that class — and only that
206
+ # class — the message is what tells a timeout apart from a real JS error.
168
207
  def timeout_error?(error)
169
- return true if error.message.to_s =~ /tim(e|ed)\s*out/i
208
+ name = error.class.name.to_s
209
+ return true if TIMEOUT_ERROR_CLASS_NAMES.include?(name)
170
210
 
171
- # Ferrum raises its own timeout/JS errors; match by class name without a
172
- # hard dependency on the Ferrum constants (ferrum is only a dev dep here).
173
- error.class.name.to_s =~ /Ferrum::(Timeout|JavaScript)Error/ &&
174
- error.message.to_s =~ /timed out promise/i
211
+ name == "Ferrum::JavaScriptError" && error.message.to_s.match?(/timed out promise/i)
175
212
  end
176
213
  end
177
214
  end
@@ -1,14 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AxeCuprite
4
+ # Recursively freezes a parsed-JSON tree (hashes/arrays of scalars) so the
5
+ # read-only wrappers below can hand out `raw`/`to_h` without a caller being
6
+ # able to mutate internal state (which, via memoization, could otherwise
7
+ # desync `violations`/`incomplete` from `raw`). Idempotent and cheap on the
8
+ # slimmed payload we carry back.
9
+ module DeepFreeze
10
+ module_function
11
+
12
+ def call(obj)
13
+ case obj
14
+ when Hash
15
+ obj.each { |k, v| [k, v].each { |o| call(o) } }
16
+ when Array
17
+ obj.each { |v| call(v) }
18
+ end
19
+ obj.freeze
20
+ end
21
+ end
22
+
4
23
  # Wraps the (slimmed) payload returned by axe.run. We deliberately only carry
5
24
  # `violations` and `incomplete` across the CDP boundary plus a little metadata
6
25
  # — the full results object (with `passes`/`inapplicable`) can be huge.
26
+ #
27
+ # The wrappers are read-only: `@raw` is deep-frozen at construction, so `raw`
28
+ # and `to_h` expose the live underlying hash safely (callers cannot mutate it).
7
29
  class Results
8
30
  attr_reader :raw
9
31
 
10
32
  def initialize(raw)
11
- @raw = raw || {}
33
+ @raw = DeepFreeze.call(raw || {})
12
34
  end
13
35
 
14
36
  def violations
@@ -47,7 +69,7 @@ module AxeCuprite
47
69
  attr_reader :raw
48
70
 
49
71
  def initialize(raw)
50
- @raw = raw || {}
72
+ @raw = DeepFreeze.call(raw || {})
51
73
  end
52
74
 
53
75
  def id
@@ -88,7 +110,7 @@ module AxeCuprite
88
110
  attr_reader :raw
89
111
 
90
112
  def initialize(raw)
91
- @raw = raw || {}
113
+ @raw = DeepFreeze.call(raw || {})
92
114
  end
93
115
 
94
116
  # axe gives `target` as an array of CSS selectors (one per frame depth).
@@ -137,7 +159,7 @@ module AxeCuprite
137
159
  attr_reader :raw
138
160
 
139
161
  def initialize(raw)
140
- @raw = raw || {}
162
+ @raw = DeepFreeze.call(raw || {})
141
163
  end
142
164
 
143
165
  def fg_color
@@ -193,7 +193,7 @@ module AxeCuprite
193
193
 
194
194
  def format_node(node)
195
195
  lines = [" - #{node.selector}"]
196
- lines << " #{truncate(node.html)}" if node.html
196
+ lines << " #{truncate(node.html)}" if node.html && config.include_html
197
197
 
198
198
  if (cd = node.contrast_data)
199
199
  lines << " contrast #{cd.contrast_ratio}:1 (needs #{cd.expected_contrast_ratio}:1) — " \
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "open-uri"
3
+ require "digest"
4
4
  require "fileutils"
5
5
  require "json"
6
+ require "open-uri"
7
+ require "rubygems/package"
8
+ require "stringio"
9
+ require "zlib"
6
10
 
7
11
  namespace :axe do
8
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)"
@@ -15,40 +19,60 @@ namespace :axe do
15
19
  task :version do
16
20
  puts AxeCupriteVendor.vendored_version
17
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
18
27
  end
19
28
 
20
29
  # Helpers for vendoring axe-core. Kept in a module so the rake tasks stay thin
21
30
  # and the logic is easy to read. Never used at runtime — vendoring is a
22
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.
23
41
  module AxeCupriteVendor
24
42
  VENDOR_DIR = File.expand_path("../vendor", __dir__)
25
43
  AXE_JS = File.join(VENDOR_DIR, "axe.min.js")
44
+ AXE_SHA512 = File.join(VENDOR_DIR, "axe.min.js.sha512")
26
45
  AXE_LICENSE = File.join(VENDOR_DIR, "axe-core-LICENSE.txt")
27
46
  VERSION_FILE = File.expand_path("../version.rb", __dir__)
28
47
  REGISTRY = "https://registry.npmjs.org/axe-core"
29
- CDN = "https://unpkg.com/axe-core"
30
48
 
31
49
  module_function
32
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
+
33
57
  def update!(version)
58
+ abort "Invalid version: #{version.inspect}" unless version.match?(VERSION_FORMAT)
59
+
34
60
  FileUtils.mkdir_p(VENDOR_DIR)
35
61
 
36
- puts "Fetching axe-core@#{version} ..."
37
- js = download("#{CDN}@#{version}/axe.min.js")
38
- unless js =~ /axe v#{Regexp.escape(version)}\b/
39
- warn "Warning: downloaded axe.min.js banner does not mention v#{version} — continuing anyway."
40
- end
41
- license = download("#{CDN}@#{version}/LICENSE")
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)
42
69
 
43
70
  File.write(AXE_JS, js)
44
71
  File.write(AXE_LICENSE, license)
72
+ File.write(AXE_SHA512, "#{Digest::SHA512.hexdigest(js)} axe.min.js\n")
45
73
  bump_version_constant(version)
46
74
 
47
- puts "Vendored axe-core #{version}:"
48
- puts " #{AXE_JS} (#{js.bytesize} bytes)"
49
- puts " #{AXE_LICENSE}"
50
- puts " updated AXE_CORE_VERSION in #{VERSION_FILE}"
51
- puts "Remember to note the version bump in CHANGELOG.md."
75
+ report(version, js)
52
76
  end
53
77
 
54
78
  def latest_version
@@ -59,8 +83,71 @@ module AxeCupriteVendor
59
83
  File.read(AXE_JS)[/axe v([0-9][0-9.]*)/, 1] || "unknown"
60
84
  end
61
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
+
62
149
  def download(url)
63
- URI.parse(url).open(&:read)
150
+ URI.parse(url).open("rb", &:read)
64
151
  rescue OpenURI::HTTPError => e
65
152
  abort "Failed to download #{url}: #{e.message}"
66
153
  end
@@ -70,4 +157,13 @@ module AxeCupriteVendor
70
157
  updated = contents.sub(/(AXE_CORE_VERSION\s*=\s*)"[^"]*"/, %(\\1"#{version}"))
71
158
  File.write(VERSION_FILE, updated)
72
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
73
169
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AxeCuprite
4
4
  # Version of the axe-cuprite gem itself.
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.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`.
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.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdullah Hashim