puppeteer-bidi 0.0.3.beta2 → 0.0.3.beta3
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 +4 -4
- data/lib/puppeteer/bidi/reactor_runner.rb +12 -1
- data/lib/puppeteer/bidi/transport.rb +4 -1
- data/lib/puppeteer/bidi/version.rb +1 -1
- data/sig/_external.rbs +1 -0
- metadata +1 -4
- data/AGENTS.md +0 -44
- data/development/generate_api_coverage.rb +0 -412
- data/development/puppeteer_revision.txt +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb435eba199162f3270ff09d0efeaf728935717f0c690a0549f176f1be06e072
|
|
4
|
+
data.tar.gz: 7a39e4c65471997c4d37f4558459121a9370769dbf341b7f45b6d2441175b60e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d9b44c59dda746bae25de8ad9e37c9576867fd5f39b434e39e0ea2cb1ce30d05e943496d787e9cc254d5747af1ce8514818dce67c5778861345617788c2b6af
|
|
7
|
+
data.tar.gz: 5d80cc021677292638eb0103cf7aca8f9c87309fcb858425360b1ac6f937f2d5d1e5386c5b30e738589fc787ab7f609a271af18d682ac9452b95ae5fec25d765
|
|
@@ -116,7 +116,18 @@ module Puppeteer
|
|
|
116
116
|
raise Error, "ReactorRunner is closed"
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
# Ruby 4.0: Async::Promise#wait can wake spuriously; retry until resolved.
|
|
120
|
+
loop do
|
|
121
|
+
begin
|
|
122
|
+
return promise.wait
|
|
123
|
+
rescue TypeError => e
|
|
124
|
+
raise unless e.message == "exception class/object expected"
|
|
125
|
+
next unless promise.resolved?
|
|
126
|
+
value = promise.value
|
|
127
|
+
raise value if value.is_a?(Exception)
|
|
128
|
+
return value
|
|
129
|
+
end
|
|
130
|
+
end
|
|
120
131
|
end
|
|
121
132
|
|
|
122
133
|
# @rbs return: void
|
|
@@ -108,8 +108,11 @@ module Puppeteer
|
|
|
108
108
|
warn "Failed to parse BiDi message: #{e.message}"
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
|
+
rescue IOError, Errno::ECONNRESET, Errno::EPIPE
|
|
112
|
+
# Connection closed - this is expected during shutdown, no need to warn
|
|
111
113
|
rescue => e
|
|
112
|
-
warn
|
|
114
|
+
# Only warn for unexpected errors if we weren't intentionally closed
|
|
115
|
+
warn "Transport receive error: #{e.message}" unless @closed
|
|
113
116
|
ensure
|
|
114
117
|
close unless @closed
|
|
115
118
|
end
|
data/sig/_external.rbs
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: puppeteer-bidi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.3.
|
|
4
|
+
version: 0.0.3.beta3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- YusukeIwaki
|
|
@@ -77,15 +77,12 @@ extra_rdoc_files: []
|
|
|
77
77
|
files:
|
|
78
78
|
- ".rspec"
|
|
79
79
|
- ".rubocop.yml"
|
|
80
|
-
- AGENTS.md
|
|
81
80
|
- API_COVERAGE.md
|
|
82
81
|
- DEVELOPMENT.md
|
|
83
82
|
- LICENSE.txt
|
|
84
83
|
- README.md
|
|
85
84
|
- Rakefile
|
|
86
85
|
- Steepfile
|
|
87
|
-
- development/generate_api_coverage.rb
|
|
88
|
-
- development/puppeteer_revision.txt
|
|
89
86
|
- lib/puppeteer/bidi.rb
|
|
90
87
|
- lib/puppeteer/bidi/async_utils.rb
|
|
91
88
|
- lib/puppeteer/bidi/browser.rb
|
data/AGENTS.md
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# Repository Guidelines
|
|
2
|
-
|
|
3
|
-
## Start Here (Project-Specific Guidance)
|
|
4
|
-
|
|
5
|
-
- Read `CLAUDE.md` and `CLAUDE/` first; they define the core async architecture, porting workflow, and testing strategy.
|
|
6
|
-
- Ruby: requires `>= 3.2` (CI covers 3.2–3.4). Prefer a version manager (`rbenv`, `asdf`, etc.) over system Ruby.
|
|
7
|
-
|
|
8
|
-
## Project Structure & Module Organization
|
|
9
|
-
|
|
10
|
-
- `lib/puppeteer/bidi/`: user-facing API (sync, calls `.wait`).
|
|
11
|
-
- `lib/puppeteer/bidi/core/`: low-level BiDi core (async, returns `Async::Task`).
|
|
12
|
-
- `spec/integration/`: browser-driven specs; fixtures in `spec/assets/`.
|
|
13
|
-
|
|
14
|
-
## Build, Test, and Development Commands
|
|
15
|
-
|
|
16
|
-
- See `DEVELOPMENT.md` for the full command list and environment variables.
|
|
17
|
-
- Run RSpec via `rbenv exec bundle exec rspec ...` when using rbenv.
|
|
18
|
-
|
|
19
|
-
## Coding Style & Naming Conventions
|
|
20
|
-
|
|
21
|
-
- Ruby: 2-space indent, double-quoted strings, ~120 char lines; follow RuboCop (`.rubocop.yml`).
|
|
22
|
-
- BiDi-only: do not introduce CDP-related ports.
|
|
23
|
-
- Async: core returns `Async::Task`; upper layer must call `.wait` on every core call (see `CLAUDE/two_layer_architecture.md`).
|
|
24
|
-
- Reviews to watch: WS messages can be handled out-of-order; “wait for event” code must not hang (register listeners before commands, handle “already happened”, cancel on errors).
|
|
25
|
-
|
|
26
|
-
## Testing Guidelines
|
|
27
|
-
|
|
28
|
-
- Prefer `spec/integration/` and the shared-browser `with_test_state` pattern (see `CLAUDE/testing_strategy.md`).
|
|
29
|
-
- Use `pending` for browser limitations vs `skip` for unimplemented features.
|
|
30
|
-
|
|
31
|
-
## Commit & Pull Request Guidelines
|
|
32
|
-
|
|
33
|
-
- See `DEVELOPMENT.md` for commit, PR, and release conventions.
|
|
34
|
-
|
|
35
|
-
## Agent Notes (Porting/Review)
|
|
36
|
-
|
|
37
|
-
- When porting from upstream TS, mirror optional vs default fields: defaults are for validation, and optional keys should be omitted from payloads unless explicitly provided.
|
|
38
|
-
- Match upstream error messages as closely as possible (including interpolated values) so tests align with Puppeteer.
|
|
39
|
-
- In core layer option checks, use key presence (`options.key?`) when upstream uses `'in'` to distinguish "missing" from `nil`.
|
|
40
|
-
- During reviews, compare both implementation (`packages/puppeteer-core/src/bidi/*.ts`) and tests (`test/src/page.spec.ts`) to catch behavior parity gaps.
|
|
41
|
-
|
|
42
|
-
## Security & Configuration Tips
|
|
43
|
-
|
|
44
|
-
- Do not commit credentials; review network-fetched changes (e.g., `scripts/update_injected_source.rb` → `lib/puppeteer/bidi/injected.js`) carefully.
|
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require "optparse"
|
|
5
|
-
require "pathname"
|
|
6
|
-
require "time"
|
|
7
|
-
|
|
8
|
-
ROOT = Pathname.new(__dir__).join("..").expand_path
|
|
9
|
-
CACHE_SCHEMA_VERSION = 1
|
|
10
|
-
|
|
11
|
-
def run_capture(*command, chdir: nil)
|
|
12
|
-
output = if chdir
|
|
13
|
-
IO.popen(command, chdir: chdir.to_s, err: %i[child out], &:read)
|
|
14
|
-
else
|
|
15
|
-
IO.popen(command, err: %i[child out], &:read)
|
|
16
|
-
end
|
|
17
|
-
status = $?
|
|
18
|
-
raise "Command failed: #{command.join(" ")}\n#{output}" unless status&.success?
|
|
19
|
-
|
|
20
|
-
output
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def read_puppeteer_commit(puppeteer_dir)
|
|
24
|
-
run_capture("git", "rev-parse", "HEAD", chdir: puppeteer_dir.to_s).strip
|
|
25
|
-
rescue StandardError
|
|
26
|
-
"unknown"
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def find_puppeteer_doc_paths(puppeteer_dir)
|
|
30
|
-
candidates = []
|
|
31
|
-
|
|
32
|
-
api_md = puppeteer_dir.join("docs/api.md")
|
|
33
|
-
candidates << api_md if api_md.file?
|
|
34
|
-
|
|
35
|
-
api_dir = puppeteer_dir.join("docs/api")
|
|
36
|
-
if api_dir.directory?
|
|
37
|
-
Dir.glob(api_dir.join("**/*.md")).sort.each do |path|
|
|
38
|
-
candidates << Pathname.new(path)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
candidates.uniq
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def extract_frontmatter_value(markdown, key)
|
|
46
|
-
in_frontmatter = false
|
|
47
|
-
markdown.each_line do |line|
|
|
48
|
-
stripped = line.strip
|
|
49
|
-
if stripped == "---"
|
|
50
|
-
in_frontmatter = !in_frontmatter
|
|
51
|
-
next
|
|
52
|
-
end
|
|
53
|
-
next unless in_frontmatter
|
|
54
|
-
|
|
55
|
-
next unless stripped.start_with?("#{key}:")
|
|
56
|
-
|
|
57
|
-
value = stripped.delete_prefix("#{key}:").strip
|
|
58
|
-
value = value[1..-2] if (value.start_with?('"') && value.end_with?('"')) || (value.start_with?("'") && value.end_with?("'"))
|
|
59
|
-
return value.strip
|
|
60
|
-
end
|
|
61
|
-
nil
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def extract_heading_api_references(markdown, source:)
|
|
65
|
-
refs = []
|
|
66
|
-
|
|
67
|
-
markdown.each_line do |line|
|
|
68
|
-
next unless line.start_with?("#")
|
|
69
|
-
|
|
70
|
-
heading = line.sub(/\A#+\s+/, "").strip
|
|
71
|
-
next if heading.empty?
|
|
72
|
-
|
|
73
|
-
token = heading.split(/[\s(:—-]/, 2).first.to_s.strip
|
|
74
|
-
next if token.empty?
|
|
75
|
-
next if token.start_with?("event:", "type:", "class:", "interface:")
|
|
76
|
-
|
|
77
|
-
token = token.delete_suffix("method")
|
|
78
|
-
token = token.delete_suffix("property")
|
|
79
|
-
token = token.delete_suffix("class")
|
|
80
|
-
token = token.delete_suffix("()")
|
|
81
|
-
token = token.sub(/\Anew\s+/, "")
|
|
82
|
-
token = token[1..-2] if token.start_with?("`") && token.end_with?("`") && token.length >= 2
|
|
83
|
-
|
|
84
|
-
owner = nil
|
|
85
|
-
member = nil
|
|
86
|
-
|
|
87
|
-
if token.include?(".")
|
|
88
|
-
owner, member = token.split(".", 2)
|
|
89
|
-
elsif token.include?("#")
|
|
90
|
-
owner, member = token.split("#", 2)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
next if owner.nil? || member.nil?
|
|
94
|
-
next if owner.empty? || member.empty?
|
|
95
|
-
|
|
96
|
-
refs << { "owner" => owner, "member" => member, "source" => source }
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
refs
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def extract_puppeteer_api(puppeteer_dir)
|
|
103
|
-
doc_paths = find_puppeteer_doc_paths(puppeteer_dir)
|
|
104
|
-
raise "Could not find Puppeteer docs under #{puppeteer_dir}" if doc_paths.empty?
|
|
105
|
-
|
|
106
|
-
entries = []
|
|
107
|
-
doc_paths.each do |path|
|
|
108
|
-
markdown = path.read
|
|
109
|
-
rel = path.relative_path_from(puppeteer_dir).to_s
|
|
110
|
-
|
|
111
|
-
sidebar_label = extract_frontmatter_value(markdown, "sidebar_label")
|
|
112
|
-
if sidebar_label && (sidebar_label.include?(".") || sidebar_label.include?("#"))
|
|
113
|
-
token = sidebar_label.strip
|
|
114
|
-
token = token[1..-2] if token.start_with?("`") && token.end_with?("`") && token.length >= 2
|
|
115
|
-
owner = nil
|
|
116
|
-
member = nil
|
|
117
|
-
if token.include?(".")
|
|
118
|
-
owner, member = token.split(".", 2)
|
|
119
|
-
elsif token.include?("#")
|
|
120
|
-
owner, member = token.split("#", 2)
|
|
121
|
-
end
|
|
122
|
-
if owner && member && !owner.empty? && !member.empty?
|
|
123
|
-
entries << { "owner" => owner, "member" => member, "source" => rel }
|
|
124
|
-
next
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
entries.concat(extract_heading_api_references(markdown, source: rel))
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
dedup = {}
|
|
132
|
-
entries.each do |e|
|
|
133
|
-
key = "#{e["owner"]}.#{e["member"]}"
|
|
134
|
-
dedup[key] ||= e
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
dedup.values.sort_by { |e| [e["owner"].downcase, e["member"].downcase] }
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def camel_to_snake(name)
|
|
141
|
-
name
|
|
142
|
-
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
143
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
144
|
-
.tr("-", "_")
|
|
145
|
-
.downcase
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
SPECIAL_MEMBER_MAPPINGS = {
|
|
149
|
-
"$" => "query_selector",
|
|
150
|
-
"$$" => "query_selector_all",
|
|
151
|
-
"$eval" => "eval_on_selector",
|
|
152
|
-
"$$eval" => "eval_on_selector_all",
|
|
153
|
-
"waitForSelector" => "wait_for_selector",
|
|
154
|
-
"waitForXPath" => "wait_for_xpath",
|
|
155
|
-
"waitForFunction" => "wait_for_function",
|
|
156
|
-
"waitForNavigation" => "wait_for_navigation",
|
|
157
|
-
"waitForRequest" => "wait_for_request",
|
|
158
|
-
"waitForResponse" => "wait_for_response",
|
|
159
|
-
"waitForFileChooser" => "wait_for_file_chooser",
|
|
160
|
-
"evaluateHandle" => "evaluate_handle",
|
|
161
|
-
"evaluateOnNewDocument" => "evaluate_on_new_document",
|
|
162
|
-
"setContent" => "set_content",
|
|
163
|
-
"setViewport" => "set_viewport",
|
|
164
|
-
"setUserAgent" => "set_user_agent",
|
|
165
|
-
"setExtraHTTPHeaders" => "set_extra_http_headers",
|
|
166
|
-
"isClosed" => "closed?"
|
|
167
|
-
}.freeze
|
|
168
|
-
|
|
169
|
-
def ruby_member_candidates(node_member)
|
|
170
|
-
mapped = SPECIAL_MEMBER_MAPPINGS[node_member]
|
|
171
|
-
return [mapped] if mapped
|
|
172
|
-
|
|
173
|
-
snake = camel_to_snake(node_member)
|
|
174
|
-
candidates = [snake]
|
|
175
|
-
|
|
176
|
-
if snake.start_with?("is_") && !snake.end_with?("?")
|
|
177
|
-
candidates << "#{snake.delete_prefix("is_")}?"
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
candidates.uniq
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
NODE_OWNER_ALIASES = {
|
|
184
|
-
"puppeteer" => "Puppeteer",
|
|
185
|
-
"Puppeteer" => "Puppeteer",
|
|
186
|
-
"PuppeteerNode" => "Puppeteer",
|
|
187
|
-
"browser" => "Browser",
|
|
188
|
-
"Browser" => "Browser",
|
|
189
|
-
"browsercontext" => "BrowserContext",
|
|
190
|
-
"browserContext" => "BrowserContext",
|
|
191
|
-
"BrowserContext" => "BrowserContext",
|
|
192
|
-
"page" => "Page",
|
|
193
|
-
"Page" => "Page",
|
|
194
|
-
"frame" => "Frame",
|
|
195
|
-
"Frame" => "Frame",
|
|
196
|
-
"elementhandle" => "ElementHandle",
|
|
197
|
-
"elementHandle" => "ElementHandle",
|
|
198
|
-
"ElementHandle" => "ElementHandle",
|
|
199
|
-
"jshandle" => "JSHandle",
|
|
200
|
-
"jsHandle" => "JSHandle",
|
|
201
|
-
"JSHandle" => "JSHandle",
|
|
202
|
-
"keyboard" => "Keyboard",
|
|
203
|
-
"Keyboard" => "Keyboard",
|
|
204
|
-
"mouse" => "Mouse",
|
|
205
|
-
"Mouse" => "Mouse",
|
|
206
|
-
"httprequest" => "HTTPRequest",
|
|
207
|
-
"httpRequest" => "HTTPRequest",
|
|
208
|
-
"HTTPRequest" => "HTTPRequest",
|
|
209
|
-
"httpresponse" => "HTTPResponse",
|
|
210
|
-
"httpResponse" => "HTTPResponse",
|
|
211
|
-
"HTTPResponse" => "HTTPResponse",
|
|
212
|
-
"filechooser" => "FileChooser",
|
|
213
|
-
"fileChooser" => "FileChooser",
|
|
214
|
-
"FileChooser" => "FileChooser"
|
|
215
|
-
# Note: Target is excluded from coverage tracking due to significant
|
|
216
|
-
# implementation differences between Node.js and Ruby versions.
|
|
217
|
-
}.freeze
|
|
218
|
-
|
|
219
|
-
def canonical_node_owner(owner)
|
|
220
|
-
NODE_OWNER_ALIASES[owner] || NODE_OWNER_ALIASES[owner.downcase] || owner
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
RUBY_OWNER_CONSTANTS = {
|
|
224
|
-
"Puppeteer" => "Puppeteer::Bidi",
|
|
225
|
-
"Browser" => "Puppeteer::Bidi::Browser",
|
|
226
|
-
"BrowserContext" => "Puppeteer::Bidi::BrowserContext",
|
|
227
|
-
"Page" => "Puppeteer::Bidi::Page",
|
|
228
|
-
"Frame" => "Puppeteer::Bidi::Frame",
|
|
229
|
-
"ElementHandle" => "Puppeteer::Bidi::ElementHandle",
|
|
230
|
-
"JSHandle" => "Puppeteer::Bidi::JSHandle",
|
|
231
|
-
"Keyboard" => "Puppeteer::Bidi::Keyboard",
|
|
232
|
-
"Mouse" => "Puppeteer::Bidi::Mouse",
|
|
233
|
-
"HTTPRequest" => "Puppeteer::Bidi::HTTPRequest",
|
|
234
|
-
"HTTPResponse" => "Puppeteer::Bidi::HTTPResponse",
|
|
235
|
-
"FileChooser" => "Puppeteer::Bidi::FileChooser"
|
|
236
|
-
# Note: Target is excluded from coverage tracking due to significant
|
|
237
|
-
# implementation differences between Node.js and Ruby versions.
|
|
238
|
-
}.freeze
|
|
239
|
-
|
|
240
|
-
def safe_constantize(name)
|
|
241
|
-
name.split("::").inject(Object) { |mod, const_name| mod.const_get(const_name) }
|
|
242
|
-
rescue NameError
|
|
243
|
-
nil
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def extract_ruby_public_api
|
|
247
|
-
$LOAD_PATH.unshift(ROOT.join("lib").to_s)
|
|
248
|
-
require "puppeteer/bidi"
|
|
249
|
-
|
|
250
|
-
api = {}
|
|
251
|
-
lib_root = ROOT.join("lib").to_s
|
|
252
|
-
|
|
253
|
-
RUBY_OWNER_CONSTANTS.each do |label, const_name|
|
|
254
|
-
constant = safe_constantize(const_name)
|
|
255
|
-
next unless constant
|
|
256
|
-
|
|
257
|
-
if constant.is_a?(Module) && !constant.is_a?(Class)
|
|
258
|
-
methods = constant.singleton_methods(true).map(&:to_s)
|
|
259
|
-
methods.select! do |method_name|
|
|
260
|
-
method_obj = constant.method(method_name)
|
|
261
|
-
owner_name = method_obj.owner.name
|
|
262
|
-
owner_name&.start_with?("Puppeteer::Bidi") ||
|
|
263
|
-
(method_obj.source_location && method_obj.source_location.first.start_with?(lib_root))
|
|
264
|
-
rescue NameError
|
|
265
|
-
false
|
|
266
|
-
end
|
|
267
|
-
api[label] = { kind: :module, const_name: const_name, methods: methods.sort.uniq }
|
|
268
|
-
next
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
methods = constant.public_instance_methods(true).map(&:to_s)
|
|
272
|
-
methods.select! do |method_name|
|
|
273
|
-
owner_name = constant.instance_method(method_name).owner.name
|
|
274
|
-
owner_name&.start_with?("Puppeteer::Bidi")
|
|
275
|
-
rescue NameError
|
|
276
|
-
false
|
|
277
|
-
end
|
|
278
|
-
api[label] = { kind: :class, const_name: const_name, methods: methods.sort.uniq }
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
api
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
def load_cache(cache_file, expected_commit:)
|
|
285
|
-
return nil unless cache_file.file?
|
|
286
|
-
|
|
287
|
-
data = JSON.parse(cache_file.read)
|
|
288
|
-
return nil unless data.is_a?(Hash)
|
|
289
|
-
return nil unless data["schema_version"] == CACHE_SCHEMA_VERSION
|
|
290
|
-
return nil unless data["puppeteer_commit"] == expected_commit
|
|
291
|
-
|
|
292
|
-
entries = data["entries"]
|
|
293
|
-
return nil unless entries.is_a?(Array)
|
|
294
|
-
|
|
295
|
-
entries
|
|
296
|
-
rescue JSON::ParserError
|
|
297
|
-
nil
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
def write_cache(cache_file, puppeteer_commit:, entries:)
|
|
301
|
-
cache_file.parent.mkpath
|
|
302
|
-
payload = {
|
|
303
|
-
"schema_version" => CACHE_SCHEMA_VERSION,
|
|
304
|
-
"puppeteer_commit" => puppeteer_commit,
|
|
305
|
-
"generated_at" => Time.now.utc.iso8601,
|
|
306
|
-
"entries" => entries
|
|
307
|
-
}
|
|
308
|
-
cache_file.write(JSON.pretty_generate(payload) + "\n")
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
def generate_markdown(puppeteer_commit:, entries:, ruby_api:)
|
|
312
|
-
supported_owners = RUBY_OWNER_CONSTANTS.keys.to_h { |k| [k, true] }
|
|
313
|
-
filtered = entries.select { |e| supported_owners[canonical_node_owner(e["owner"])] }
|
|
314
|
-
grouped = filtered.group_by { |e| canonical_node_owner(e["owner"]) }
|
|
315
|
-
|
|
316
|
-
total = 0
|
|
317
|
-
supported = 0
|
|
318
|
-
|
|
319
|
-
sections = []
|
|
320
|
-
|
|
321
|
-
grouped.keys.sort_by(&:downcase).each do |node_owner_label|
|
|
322
|
-
group = grouped.fetch(node_owner_label)
|
|
323
|
-
ruby_owner_const = RUBY_OWNER_CONSTANTS[node_owner_label]
|
|
324
|
-
ruby_owner = ruby_owner_const ? ruby_api[node_owner_label] : nil
|
|
325
|
-
|
|
326
|
-
heading = ruby_owner_const ? "#{node_owner_label} (#{ruby_owner_const})" : node_owner_label
|
|
327
|
-
section_lines = []
|
|
328
|
-
section_lines << "## #{heading}"
|
|
329
|
-
section_lines << ""
|
|
330
|
-
section_lines << "| Node.js | Ruby | Supported |"
|
|
331
|
-
section_lines << "| --- | --- | :---: |"
|
|
332
|
-
|
|
333
|
-
ruby_methods = ruby_owner ? ruby_owner.fetch(:methods) : []
|
|
334
|
-
ruby_kind = ruby_owner ? ruby_owner.fetch(:kind) : nil
|
|
335
|
-
|
|
336
|
-
group.sort_by { |e| e["member"].downcase }.each do |entry|
|
|
337
|
-
node_owner = entry.fetch("owner")
|
|
338
|
-
node_member = entry.fetch("member")
|
|
339
|
-
node_ref = "#{node_owner}.#{node_member}"
|
|
340
|
-
|
|
341
|
-
ruby_candidates = ruby_member_candidates(node_member)
|
|
342
|
-
ruby_supported_method = ruby_candidates.find { |m| ruby_methods.include?(m) }
|
|
343
|
-
|
|
344
|
-
total += 1
|
|
345
|
-
if ruby_supported_method
|
|
346
|
-
supported += 1
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
ruby_ref = "-"
|
|
350
|
-
if ruby_owner_const && ruby_candidates.any?
|
|
351
|
-
separator = ruby_kind == :module ? "." : "#"
|
|
352
|
-
ruby_method_name = ruby_supported_method || ruby_candidates.first
|
|
353
|
-
ruby_ref = "#{ruby_owner_const}#{separator}#{ruby_method_name}"
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
status = ruby_supported_method ? "✅" : "❌"
|
|
357
|
-
section_lines << "| `#{node_ref}` | `#{ruby_ref}` | #{status} |"
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
section_lines << ""
|
|
361
|
-
sections << section_lines.join("\n")
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
coverage = total.zero? ? 0.0 : (supported.to_f / total) * 100.0
|
|
365
|
-
lines = []
|
|
366
|
-
lines << "# API Coverage"
|
|
367
|
-
lines << ""
|
|
368
|
-
lines << "- Puppeteer commit: `#{puppeteer_commit}`"
|
|
369
|
-
lines << "- Generated by: `development/generate_api_coverage.rb`"
|
|
370
|
-
lines << "- Coverage: `#{supported}/#{total}` (`#{format("%.2f", coverage)}%`)"
|
|
371
|
-
lines << ""
|
|
372
|
-
lines << sections.join("\n")
|
|
373
|
-
|
|
374
|
-
lines.join("\n") + "\n"
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
options = {
|
|
378
|
-
puppeteer_dir: ROOT.join("development", "puppeteer").to_s,
|
|
379
|
-
cache_dir: ROOT.join("development", "cache").to_s,
|
|
380
|
-
output: ROOT.join("API_COVERAGE.md").to_s
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
OptionParser.new do |opts|
|
|
384
|
-
opts.banner = "Usage: bundle exec ruby development/generate_api_coverage.rb [options]"
|
|
385
|
-
opts.on("--puppeteer-dir=PATH", "Path to a checked out puppeteer/puppeteer repo") { |v| options[:puppeteer_dir] = v }
|
|
386
|
-
opts.on("--cache-dir=PATH", "Cache directory (default: development/cache)") { |v| options[:cache_dir] = v }
|
|
387
|
-
opts.on("--output=PATH", "Output path (default: API_COVERAGE.md)") { |v| options[:output] = v }
|
|
388
|
-
end.parse!
|
|
389
|
-
|
|
390
|
-
puppeteer_dir = Pathname.new(options.fetch(:puppeteer_dir)).expand_path
|
|
391
|
-
cache_dir = Pathname.new(options.fetch(:cache_dir)).expand_path
|
|
392
|
-
output_path = Pathname.new(options.fetch(:output)).expand_path
|
|
393
|
-
|
|
394
|
-
raise "Puppeteer repo not found: #{puppeteer_dir}" unless puppeteer_dir.directory?
|
|
395
|
-
|
|
396
|
-
puppeteer_commit = read_puppeteer_commit(puppeteer_dir)
|
|
397
|
-
cache_file = cache_dir.join("puppeteer_api.json")
|
|
398
|
-
|
|
399
|
-
entries = load_cache(cache_file, expected_commit: puppeteer_commit)
|
|
400
|
-
unless entries
|
|
401
|
-
entries = extract_puppeteer_api(puppeteer_dir)
|
|
402
|
-
write_cache(cache_file, puppeteer_commit: puppeteer_commit, entries: entries)
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
ruby_api = extract_ruby_public_api
|
|
406
|
-
output_path.write(
|
|
407
|
-
generate_markdown(
|
|
408
|
-
puppeteer_commit: puppeteer_commit,
|
|
409
|
-
entries: entries,
|
|
410
|
-
ruby_api: ruby_api
|
|
411
|
-
)
|
|
412
|
-
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
7d750c25cb29764f2fb31cb90b750a8eec350199
|