bidi2pdf 0.1.13 → 0.1.15

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.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +53 -0
  3. data/.devcontainer/devcontainer.json +51 -0
  4. data/.devcontainer/docker-compose.yml +33 -0
  5. data/.ruby-gemset +1 -1
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +93 -6
  8. data/README.md +80 -5
  9. data/docker/Dockerfile +1 -1
  10. data/docker/Dockerfile.slim +2 -2
  11. data/docker/chrome-driver/chrome.json +874 -0
  12. data/lib/bidi2pdf/bidi/browser_tab.rb +97 -4
  13. data/lib/bidi2pdf/bidi/buffered_web_socket_client.rb +178 -0
  14. data/lib/bidi2pdf/bidi/client.rb +12 -5
  15. data/lib/bidi2pdf/bidi/commands/base.rb +2 -0
  16. data/lib/bidi2pdf/bidi/commands/capture_screenshot.rb +33 -0
  17. data/lib/bidi2pdf/bidi/commands/set_viewport.rb +32 -0
  18. data/lib/bidi2pdf/bidi/commands.rb +2 -0
  19. data/lib/bidi2pdf/bidi/network_event.rb +12 -3
  20. data/lib/bidi2pdf/bidi/network_events.rb +9 -1
  21. data/lib/bidi2pdf/bidi/session.rb +1 -1
  22. data/lib/bidi2pdf/chromedriver_manager.rb +10 -6
  23. data/lib/bidi2pdf/cli.rb +11 -1
  24. data/lib/bidi2pdf/launcher.rb +5 -3
  25. data/lib/bidi2pdf/notifications/logging_subscriber.rb +2 -0
  26. data/lib/bidi2pdf/session_warmer.rb +377 -0
  27. data/lib/bidi2pdf/test_helpers/images.rb +8 -0
  28. data/lib/bidi2pdf/test_helpers/pdf_reader_utils.rb +1 -2
  29. data/lib/bidi2pdf/test_helpers/pdf_text_sanitizer.rb +1 -2
  30. data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rb +1 -1
  31. data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rb +22 -2
  32. data/lib/bidi2pdf/test_helpers/testcontainers/container_endpoint.rb +30 -0
  33. data/lib/bidi2pdf/test_helpers/testcontainers/testcontainers_refinement.rb +33 -0
  34. data/lib/bidi2pdf/test_helpers/testcontainers.rb +1 -0
  35. data/lib/bidi2pdf/version.rb +1 -1
  36. data/lib/bidi2pdf.rb +26 -1
  37. data/sig/bidi2pdf/bidi/add_headers_interceptor.rbs +16 -9
  38. data/sig/bidi2pdf/bidi/auth_interceptor.rbs +32 -7
  39. data/sig/bidi2pdf/bidi/browser.rbs +4 -31
  40. data/sig/bidi2pdf/bidi/browser_console_logger.rbs +59 -0
  41. data/sig/bidi2pdf/bidi/browser_tab.rbs +269 -25
  42. data/sig/bidi2pdf/bidi/buffered_web_socket_client.rbs +81 -0
  43. data/sig/bidi2pdf/bidi/client.rbs +103 -43
  44. data/sig/bidi2pdf/bidi/command_manager.rbs +5 -17
  45. data/sig/bidi2pdf/bidi/commands/add_intercept.rbs +16 -8
  46. data/sig/bidi2pdf/bidi/commands/base.rbs +49 -9
  47. data/sig/bidi2pdf/bidi/commands/browser_close.rbs +3 -1
  48. data/sig/bidi2pdf/bidi/commands/browser_create_user_context.rbs +2 -0
  49. data/sig/bidi2pdf/bidi/commands/browser_remove_user_context.rbs +19 -0
  50. data/sig/bidi2pdf/bidi/commands/browsing_context_close.rbs +8 -2
  51. data/sig/bidi2pdf/bidi/commands/browsing_context_navigate.rbs +12 -6
  52. data/sig/bidi2pdf/bidi/commands/browsing_context_print.rbs +10 -5
  53. data/sig/bidi2pdf/bidi/commands/cancel_auth.rbs +8 -2
  54. data/sig/bidi2pdf/bidi/commands/capture_screenshot.rbs +31 -0
  55. data/sig/bidi2pdf/bidi/commands/cdp_get_session.rbs +17 -0
  56. data/sig/bidi2pdf/bidi/commands/create_window.rbs +4 -0
  57. data/sig/bidi2pdf/bidi/commands/get_user_contexts.rbs +2 -0
  58. data/sig/bidi2pdf/bidi/commands/network_continue.rbs +4 -0
  59. data/sig/bidi2pdf/bidi/commands/page_print.rbs +28 -0
  60. data/sig/bidi2pdf/bidi/commands/print_parameters_validator.rbs +3 -0
  61. data/sig/bidi2pdf/bidi/commands/provide_credentials.rbs +12 -6
  62. data/sig/bidi2pdf/bidi/commands/script_evaluate.rbs +4 -0
  63. data/sig/bidi2pdf/bidi/commands/session_end.rbs +2 -0
  64. data/sig/bidi2pdf/bidi/commands/session_status.rbs +3 -1
  65. data/sig/bidi2pdf/bidi/commands/session_subscribe.rbs +4 -0
  66. data/sig/bidi2pdf/bidi/commands/set_tab_cookie.rbs +51 -23
  67. data/sig/bidi2pdf/bidi/commands/set_usercontext_cookie.rbs +17 -19
  68. data/sig/bidi2pdf/bidi/commands/set_viewport.rbs +31 -0
  69. data/sig/bidi2pdf/bidi/connection_manager.rbs +2 -1
  70. data/sig/bidi2pdf/bidi/event_manager.rbs +14 -19
  71. data/sig/bidi2pdf/bidi/interceptor.rbs +5 -0
  72. data/sig/bidi2pdf/bidi/js_logger_helper.rbs +9 -0
  73. data/sig/bidi2pdf/bidi/logger_events.rbs +21 -0
  74. data/sig/bidi2pdf/bidi/navigation_failed_events.rbs +17 -0
  75. data/sig/bidi2pdf/bidi/network_event.rbs +64 -46
  76. data/sig/bidi2pdf/bidi/network_event_formatters/network_event_console_formatter.rbs +63 -0
  77. data/sig/bidi2pdf/bidi/network_event_formatters/network_event_formatter_utils.rbs +15 -0
  78. data/sig/bidi2pdf/bidi/network_event_formatters/network_event_html_formatter.rbs +18 -0
  79. data/sig/bidi2pdf/bidi/network_event_formatters.rbs +6 -0
  80. data/sig/bidi2pdf/bidi/network_events.rbs +14 -39
  81. data/sig/bidi2pdf/bidi/session.rbs +144 -31
  82. data/sig/bidi2pdf/bidi/user_context.rbs +64 -45
  83. data/sig/bidi2pdf/bidi/web_socket_dispatcher.rbs +22 -30
  84. data/sig/bidi2pdf/chromedriver_manager.rbs +61 -22
  85. data/sig/bidi2pdf/cli.rbs +22 -2
  86. data/sig/bidi2pdf/dsl.rbs +34 -0
  87. data/sig/bidi2pdf/launcher.rbs +39 -1
  88. data/sig/bidi2pdf/notifications/event.rbs +40 -0
  89. data/sig/bidi2pdf/notifications/instrumenter.rbs +21 -0
  90. data/sig/bidi2pdf/notifications/logging_subscriber.rbs +41 -0
  91. data/sig/bidi2pdf/notifications.rbs +24 -0
  92. data/sig/bidi2pdf/process_tree.rbs +2 -2
  93. data/sig/bidi2pdf/session_runner.rbs +41 -1
  94. data/sig/bidi2pdf/session_warmer.rbs +221 -0
  95. data/sig/bidi2pdf/test_helpers/configuration.rbs +51 -0
  96. data/sig/bidi2pdf/test_helpers/images/extractor.rbs +43 -0
  97. data/sig/bidi2pdf/test_helpers/images/image_similarity_checker.rbs +35 -0
  98. data/sig/bidi2pdf/test_helpers/images/tiff_helper.rbs +70 -0
  99. data/sig/bidi2pdf/test_helpers/images.rbs +0 -0
  100. data/sig/bidi2pdf/test_helpers/matchers/contains_pdf_image.rbs +0 -0
  101. data/sig/bidi2pdf/test_helpers/matchers/contains_pdf_text.rbs +0 -0
  102. data/sig/bidi2pdf/test_helpers/matchers/have_pdf_page_count.rbs +0 -0
  103. data/sig/bidi2pdf/test_helpers/matchers/match_pdf_text.rbs +0 -0
  104. data/sig/bidi2pdf/test_helpers/pdf_file_helper.rbs +23 -0
  105. data/sig/bidi2pdf/test_helpers/pdf_reader_utils.rbs +49 -0
  106. data/sig/bidi2pdf/test_helpers/pdf_text_sanitizer.rbs +38 -0
  107. data/sig/bidi2pdf/test_helpers/spec_paths_helper.rbs +35 -0
  108. data/sig/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rbs +43 -0
  109. data/sig/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rbs +45 -0
  110. data/sig/bidi2pdf/test_helpers/testcontainers/container_endpoint.rbs +25 -0
  111. data/sig/bidi2pdf/test_helpers/testcontainers/shared_docker_network.rbs +3 -0
  112. data/sig/bidi2pdf/test_helpers/testcontainers/testcontainers_refinement.rbs +47 -0
  113. data/sig/bidi2pdf/test_helpers/testcontainers.rbs +6 -0
  114. data/sig/bidi2pdf/test_helpers.rbs +0 -0
  115. data/sig/bidi2pdf/verbose_logger.rbs +37 -0
  116. data/sig/bidi2pdf/version.rbs +3 -0
  117. data/sig/bidi2pdf.rbs +130 -2
  118. data/tasks/generate_rbs.rake +11 -11
  119. data/tasks/release_credentials_check.rake +97 -0
  120. metadata +67 -10
  121. data/sig/bidi2pdf/utils.rbs +0 -5
@@ -0,0 +1,38 @@
1
+ module Bidi2pdf
2
+ module TestHelpers
3
+ # rubocop: disable-next Metrics/ModuleLength
4
+ module PDFTextSanitizer
5
+ def self.clean: (untyped text) -> untyped
6
+
7
+ def self.clean_pages: (untyped pdf) -> untyped
8
+
9
+ def self.normalize: (untyped text) -> untyped
10
+
11
+ def self.contains?: (untyped actual_pdf_thingy, untyped expected, ?untyped? page_number) -> (false | untyped)
12
+
13
+ def self.match_expected?: (untyped text, untyped expected) -> (false | untyped)
14
+
15
+ def self.match?: (untyped actual_pdf_thingy, untyped expected_pdf_thingy) -> (true | false)
16
+
17
+ def self.report_content_mismatch: (untyped actual, untyped expected) -> untyped
18
+
19
+ def self.content_mismatch_message: (untyped actual, untyped expected) -> untyped
20
+
21
+ def self.page_difference_message: (untyped actual_page, untyped expected_page, untyped page_idx) -> (nil | untyped)
22
+
23
+ def self.format_diff_output: (untyped diffs, untyped expected, untyped actual) -> untyped
24
+
25
+ def self.reporter: () -> untyped
26
+
27
+ private
28
+
29
+ def self.group_changed_diffs: (untyped diffs) -> untyped
30
+
31
+ def self.format_change: (untyped expected, untyped actual, untyped change) -> untyped
32
+
33
+ def self.extract_snippets: (untyped expected, untyped actual, untyped change, untyped pos) -> { context_start: untyped, context: untyped, expected_snip: untyped, actual_snip: untyped }
34
+
35
+ def self.build_output: (untyped snip_data, untyped pos) -> ::Array[::String]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ # This module provides helper methods for managing paths in test environments.
2
+ # It includes methods to retrieve directories and generate temporary file paths.
3
+ module Bidi2pdf
4
+ module TestHelpers
5
+ # This submodule contains path-related helper methods and configuration for tests.
6
+ module SpecPathsHelper
7
+ # Retrieves the directory path for Docker files.
8
+ # @return [String] the Docker directory path
9
+ def fixture_dir: () -> untyped
10
+
11
+ # Retrieves the directory path for fixtures.
12
+ # @return [String] the fixture directory path
13
+ def fixture_file: (*untyped) -> untyped
14
+
15
+ # Retrieves the directory path for specs.
16
+ # @return [String] the spec directory path
17
+ def spec_dir: () -> untyped
18
+
19
+ # Retrieves the directory path for temporary files.
20
+ # @return [String] the temporary directory path
21
+ def tmp_dir: () -> untyped
22
+
23
+ # Generates a path for a temporary file by joining the temporary directory with the given parts.
24
+ # @param [Array<String>] parts the parts of the file path to join
25
+ # @return [String] the full path to the temporary file
26
+ def tmp_file: (*untyped parts) -> untyped
27
+
28
+ # Generates a random temporary directory path.
29
+ # @param [Array<String>] dirs additional directory components to include in the path
30
+ # @param [String, nil] prefix an optional prefix for the directory name
31
+ # @return [String] the full path to the random temporary directory
32
+ def random_tmp_dir: (*untyped dirs, ?prefix: untyped?) -> untyped
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ module Bidi2pdf
2
+ module TestHelpers
3
+ module Testcontainers
4
+ class ChromedriverContainer < ::Testcontainers::DockerContainer
5
+ @docker_file: untyped
6
+
7
+ @build_dir: untyped
8
+
9
+ @wait_for: untyped
10
+
11
+ @_container: untyped
12
+
13
+ @_id: untyped
14
+
15
+ @name: untyped
16
+
17
+ @_created_at: untyped
18
+
19
+ DEFAULT_CHROMEDRIVER_PORT: 3000
20
+
21
+ DEFAULT_IMAGE: "dieters877565/chromedriver"
22
+
23
+ attr_reader docker_file: untyped
24
+
25
+ attr_reader build_dir: untyped
26
+
27
+ def initialize: (?untyped image, **untyped options) -> void
28
+
29
+ def start: () -> untyped
30
+
31
+ def port: () -> untyped
32
+
33
+ # rubocop: disable Metrics/AbcSize
34
+ def build_local_image: () -> untyped
35
+
36
+ # rubocop: disable Metrics/AbcSize
37
+ def start_local_image: () -> untyped
38
+
39
+ def session_url: (?protocol: ::String) -> untyped
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ module Bidi2pdf
2
+ module TestHelpers
3
+ module Testcontainers
4
+ module ChromedriverTestHelper
5
+ def session_url: () -> untyped
6
+
7
+ def chromedriver_container: () -> untyped
8
+ end
9
+
10
+ module SessionTestHelper
11
+ def reporter: () -> untyped
12
+
13
+ def chrome_args: () -> untyped
14
+
15
+ def create_session: (untyped session_url) -> untyped
16
+
17
+ # Full session/tab lifecycle in one call - create_session above only builds the session
18
+ # object, it never starts or closes it, and there is no after(:each) teardown for a spec
19
+ # that uses it directly. Mirrors Bidi2pdf::DSL.with_tab's own ensure-based teardown, but
20
+ # against a session_url this module already knows about (a long-lived remote-chrome
21
+ # reached over `session:` metadata, not a freshly spawned local chromedriver per call).
22
+ def with_tab: (untyped session_url) { (untyped) -> untyped } -> untyped
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ # alias the long class name
29
+ ChromedriverTestcontainer: untyped
30
+
31
+ class Object
32
+ def stop_container: (untyped container) -> (nil | untyped)
33
+
34
+ def dump_container_logs: (untyped container) -> (nil | untyped)
35
+
36
+ def stop_running_container: (untyped container) -> (nil | untyped)
37
+
38
+ def reporter: () -> untyped
39
+
40
+ def chromedriver_tests_present?: () -> untyped
41
+
42
+ def test_of_kind_present?: (untyped type) -> untyped
43
+
44
+ def start_chromedriver_container: (build_dir: untyped, mounts: untyped, shared_network: untyped, ?chromedriver_log_level: ::String) -> untyped
45
+ end
@@ -0,0 +1,25 @@
1
+ module Bidi2pdf
2
+ module TestHelpers
3
+ module Testcontainers
4
+ # Builds a URL for reaching a container's published port from the test
5
+ # process, using the container's own host/port resolution.
6
+ class ContainerEndpoint
7
+ @container: untyped
8
+
9
+ @port: untyped
10
+
11
+ def initialize: (untyped container, untyped port) -> void
12
+
13
+ def url: (?scheme: ::String, ?path: ::String) -> ::String
14
+
15
+ # Address reachable from another container on the same Docker network (e.g. remote-chrome
16
+ # reaching nginx), by alias and internal port - as opposed to #url's host-accessible,
17
+ # externally-mapped port. Uses the same @port this instance was built with, not a
18
+ # container_ports.first guess: the two methods must agree on which port they're both
19
+ # describing for the same container, or a caller comparing #url and #alias_url for what's
20
+ # meant to be the same endpoint could silently get two different services.
21
+ def alias_url: (?scheme: ::String, ?path: ::String) -> ::String
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ class Object
2
+ def reporter: () -> untyped
3
+ end
@@ -0,0 +1,47 @@
1
+ module Bidi2pdf
2
+ module TestHelpers
3
+ module TestcontainersRefinement
4
+ @aliases: untyped
5
+
6
+ @_network: untyped
7
+
8
+ def id: () -> untyped
9
+
10
+ def aliases: () -> untyped
11
+
12
+ def aliases=: (untyped aliases) -> untyped
13
+
14
+ def network: () -> untyped
15
+
16
+ def with_network: (untyped network) -> self
17
+
18
+ def with_network_aliases: (*untyped aliases) -> self
19
+
20
+ def container_json: () -> untyped
21
+
22
+ # Address of the Docker host at which this container's *mapped* ports
23
+ # are reachable from the test process.
24
+ #
25
+ # Resolution belongs to testcontainers: it honours TC_HOST, a tcp:/ssh:
26
+ # DOCKER_HOST, a native local daemon, and a sibling container reaching
27
+ # the daemon over the bridge gateway — and it derives #mapped_port from
28
+ # the same decision, so the two must stay on one path. We only add a
29
+ # fallback for the cases 0.2.0 leaves unresolved.
30
+ #
31
+ # This is not the address containers use to reach each other; for that
32
+ # the helpers join a shared network and address containers by alias.
33
+ def accessible_host: () -> untyped
34
+
35
+ # testcontainers-core 0.2.0 cannot resolve a host when the test process
36
+ # runs inside a container and the container under test is on a custom
37
+ # network: #container_gateway_ip hardcodes the "bridge" network key
38
+ # (docker_container.rb:1074), so #host returns nil. On the default
39
+ # bridge it can instead reach docker_container.rb:703, which calls an
40
+ # undefined `bridge_ip`. Both are fixed on testcontainers-ruby main;
41
+ # treat either as "unresolved" and fall back.
42
+ def resolved_host: () -> untyped
43
+
44
+ def _container_create_options: () -> untyped
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ module Bidi2pdf
2
+ module TestHelpers
3
+ module Testcontainers
4
+ end
5
+ end
6
+ end
File without changes
@@ -0,0 +1,37 @@
1
+ module Bidi2pdf
2
+ class VerboseLogger < SimpleDelegator
3
+ @logger: untyped
4
+
5
+ @verbosity: untyped
6
+
7
+ VERBOSITY_LEVELS: { none: 0, low: 1, medium: 2, high: 3 }
8
+
9
+ attr_reader logger: untyped
10
+
11
+ attr_reader verbosity: untyped
12
+
13
+ def initialize: (untyped logger, ?::Symbol verbosity) -> void
14
+
15
+ def verbosity=: (untyped verbosity) -> untyped
16
+
17
+ def verbosity_sym: () -> untyped
18
+
19
+ def debug1: (?untyped? progname) { (?) -> untyped } -> (nil | untyped)
20
+
21
+ def debug1?: () -> untyped
22
+
23
+ def debug1!: () -> untyped
24
+
25
+ def debug2: (?untyped? progname) { (?) -> untyped } -> (nil | untyped)
26
+
27
+ def debug2?: () -> untyped
28
+
29
+ def debug2!: () -> untyped
30
+
31
+ def debug3: (?untyped? progname) { (?) -> untyped } -> (nil | untyped)
32
+
33
+ def debug3?: () -> untyped
34
+
35
+ def debug3!: () -> untyped
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Bidi2pdf
2
+ VERSION: "0.1.15"
3
+ end
data/sig/bidi2pdf.rbs CHANGED
@@ -1,4 +1,132 @@
1
1
  module Bidi2pdf
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
2
+ self.@logger: untyped
3
+
4
+ self.@network_events_logger: untyped
5
+
6
+ self.@browser_console_logger: untyped
7
+
8
+ self.@logging_subscriber: untyped
9
+
10
+ self.@notification_service: untyped
11
+
12
+ PAPER_FORMATS_CM: { letter: { width: ::Float, height: ::Float }, legal: { width: ::Float, height: ::Float }, tabloid: { width: ::Float, height: ::Float }, ledger: { width: ::Float, height: ::Float }, a0: { width: ::Float, height: ::Float }, a1: { width: ::Float, height: ::Float }, a2: { width: ::Float, height: ::Float }, a3: { width: ::Float, height: ::Float }, a4: { width: ::Float, height: ::Float }, a5: { width: ::Float, height: ::Float }, a6: { width: ::Float, height: ::Float } }
13
+
14
+ class Error < StandardError
15
+ end
16
+
17
+ class SessionNotStartedError < Error
18
+ end
19
+
20
+ class WebsocketError < Error
21
+ end
22
+
23
+ class ClientError < WebsocketError
24
+ end
25
+
26
+ class CmdError < ClientError
27
+ @cmd: untyped
28
+
29
+ @response: untyped
30
+
31
+ attr_reader cmd: untyped
32
+
33
+ attr_reader response: untyped
34
+
35
+ def initialize: (untyped cmd, untyped response) -> void
36
+ end
37
+
38
+ class CmdResponseNotStoredError < ClientError
39
+ end
40
+
41
+ class CmdTimeoutError < ClientError
42
+ end
43
+
44
+ class PrintError < Error
45
+ end
46
+
47
+ class ScreenshotError < Error
48
+ end
49
+
50
+ class ScriptInjectionError < Error
51
+ end
52
+
53
+ class StyleInjectionError < Error
54
+ end
55
+
56
+ class NotificationsError < Error
57
+ @causes: untyped
58
+
59
+ attr_reader causes: untyped
60
+
61
+ def initialize: (untyped causes) -> void
62
+ end
63
+
64
+ class NavigationError < Error
65
+ end
66
+
67
+ class NavigationAuthError < NavigationError
68
+ @url: untyped
69
+
70
+ attr_reader url: untyped
71
+
72
+ def initialize: (untyped url, ?untyped? message) -> void
73
+ end
74
+
75
+ class NavigationTimeoutError < NavigationError
76
+ end
77
+
78
+ class NavigationNotFoundError < NavigationError
79
+ end
80
+
81
+ class NavigationDNSError < NavigationError
82
+ end
83
+
84
+ attr_accessor self.default_timeout: untyped
85
+
86
+ attr_accessor self.enable_default_logging_subscriber: untyped
87
+
88
+ attr_accessor self.log_truncate_limit: untyped
89
+
90
+ attr_accessor self.chromedriver_log_level: untyped
91
+
92
+ attr_reader self.logging_subscriber: untyped
93
+
94
+ attr_reader self.logger: untyped
95
+
96
+ attr_reader self.network_events_logger: untyped
97
+
98
+ attr_reader self.browser_console_logger: untyped
99
+
100
+ attr_reader self.notification_service: untyped
101
+
102
+ # Allow configuration through a block
103
+ def self.configure: () ?{ (untyped) -> untyped } -> untyped
104
+
105
+ def self.init: () -> untyped
106
+
107
+ # Truncates a value for safe log output - a raw url/param can be a `data:` URL whose base64
108
+ # payload is proportional to document size, and logging it whole can be large enough to choke
109
+ # CI log ingestion (confirmed live: GitHub Actions' log UI stalls badly on very long single
110
+ # lines, reading as a hung job even though the process underneath is fine).
111
+ #
112
+ # @param [Object] value The value to truncate (converted via #to_s).
113
+ # @param [Integer] limit The maximum number of bytes to keep. Defaults to
114
+ # +Bidi2pdf.log_truncate_limit+, itself configurable via +Bidi2pdf.configure+.
115
+ # @return [String] The value unchanged if short enough, otherwise a truncated prefix plus a
116
+ # byte-count marker. Truncation is byte-based (not character-based), since the goal is
117
+ # bounding actual log-entry size; a partial trailing multi-byte character is scrubbed rather
118
+ # than left as invalid UTF-8.
119
+ def self.truncate_for_log: (untyped value, ?limit: untyped) -> (untyped | ::String)
120
+
121
+ def self.translate_paper_format: (untyped format) -> { width: untyped, height: untyped }
122
+
123
+ def self.logger=: (untyped new_logger) -> untyped
124
+
125
+ def self.network_events_logger=: (untyped new_network_events_logger) -> untyped
126
+
127
+ def self.browser_console_logger=: (untyped new_browser_console_logger) -> untyped
128
+
129
+ def self.logging_subscriber=: (untyped new_logging_subscriber) -> untyped
130
+
131
+ def self.notification_service=: (untyped new_notification_service) -> untyped
4
132
  end
@@ -6,7 +6,7 @@ require "rbs"
6
6
  SOURCE_DIR = "lib"
7
7
  OUTPUT_DIR = "sig"
8
8
 
9
- # rubocop:disable Metrics/BlockLength
9
+ # rubocop:disable-next Metrics/BlockLength
10
10
  namespace :rbs do
11
11
  file_list = FileList["#{SOURCE_DIR}/**/*.rb"]
12
12
 
@@ -32,15 +32,11 @@ namespace :rbs do
32
32
  parser = RBS::Prototype::RB.new
33
33
  parser.parse input.read
34
34
 
35
- if output.file?
36
- puts "⚠️ RBS file already exists: #{rbs_path}"
37
- else
38
- puts "📝 Writing RBS file: #{rbs_path}"
35
+ puts "📝 Writing RBS file: #{rbs_path}"
39
36
 
40
- output.open("w") do |io|
41
- writer = RBS::Writer.new(out: io)
42
- writer.write(parser.decls)
43
- end
37
+ output.open("w") do |io|
38
+ writer = RBS::Writer.new(out: io)
39
+ writer.write(parser.decls)
44
40
  end
45
41
  rescue StandardError => e
46
42
  puts "❌ Error generating RBS for #{rb_path}: #{e.message}"
@@ -55,10 +51,14 @@ namespace :rbs do
55
51
 
56
52
  desc "Clean all generated RBS files"
57
53
  task :clean_rbs do
58
- FileList["#{OUTPUT_DIR}/**/*.rbs"].each do |file|
54
+ # sig/vendor/** holds hand-written signatures for third-party gems (e.g. Thor) with no
55
+ # corresponding lib/ source to regenerate from - excluded so a clean+regenerate cycle can't
56
+ # delete them with no way to get them back.
57
+ generated = FileList["#{OUTPUT_DIR}/**/*.rbs"].exclude("#{OUTPUT_DIR}/vendor/**")
58
+
59
+ generated.each do |file|
59
60
  FileUtils.rm_f(file)
60
61
  end
61
62
  puts "🧹 Cleaned up all RBS files"
62
63
  end
63
64
  end
64
- # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ # Works out which rubygems.org credential `rake release` would really push with, and refuses to go on
6
+ # when that is not what the repo's configuration says it should be.
7
+ module ReleaseCredentialsCheck
8
+ BUNDLE_CONFIG_PATH = ".bundle/config"
9
+ CREDENTIALS_PATH = File.expand_path("~/.gem/credentials")
10
+
11
+ module_function
12
+
13
+ # Ask Bundler, exactly as its own release task does (gem_helper.rb: Bundler.settings["gem.push_key"])
14
+ # - never parse .bundle/config for this. Bundler may not be reading that file at all: the official
15
+ # ruby Docker images set BUNDLE_APP_CONFIG=/usr/local/bundle, which silently replaces the repo's
16
+ # .bundle/ as the app config dir. Reading the file directly made this check report a key that
17
+ # `rake release` then did not use.
18
+ def effective_key
19
+ Bundler.settings["gem.push_key"]&.to_s&.downcase
20
+ end
21
+
22
+ # What the repo's own .bundle/config asks for - only ever compared against #effective_key.
23
+ def key_on_disk
24
+ return unless File.exist?(BUNDLE_CONFIG_PATH)
25
+
26
+ (YAML.load_file(BUNDLE_CONFIG_PATH) || {})["BUNDLE_GEM__PUSH_KEY"]&.to_s&.downcase
27
+ end
28
+
29
+ def available_keys
30
+ File.exist?(CREDENTIALS_PATH) ? (YAML.load_file(CREDENTIALS_PATH) || {}).keys.map(&:to_s) : []
31
+ end
32
+
33
+ # Always printed first, in one stream (puts only - mixing in `warn`'s stderr made lines print out
34
+ # of order once a terminal merges the two streams), so the context stays visible above whichever
35
+ # message stops the release.
36
+ def print_banner(key, keys)
37
+ puts "=== rubygems.org push credentials ==="
38
+ puts " gem.push_key Bundler will use: #{key || "(not set)"}"
39
+ puts " Bundler app config dir: #{Bundler.app_config_path}#{" (from BUNDLE_APP_CONFIG)" if ENV["BUNDLE_APP_CONFIG"]}"
40
+ puts " ~/.gem/credentials keys: #{keys.empty? ? "(none found)" : keys.join(", ")}"
41
+ puts "======================================"
42
+ end
43
+
44
+ # @return [String, nil] why the release must not go on, or nil when it may
45
+ def problem(key, keys)
46
+ ignored_config_problem(key) || credentials_problem(key, keys)
47
+ end
48
+
49
+ def ignored_config_problem(key)
50
+ wanted = key_on_disk
51
+ return if wanted.nil? || wanted == key
52
+
53
+ "#{BUNDLE_CONFIG_PATH} sets gem.push_key '#{wanted}', but Bundler is not reading that file " \
54
+ "(app config dir: #{Bundler.app_config_path}) and would push with '#{key || "the default rubygems_api_key"}'. " \
55
+ "Set BUNDLE_GEM__PUSH_KEY=#{wanted} in the environment, or unset BUNDLE_APP_CONFIG."
56
+ end
57
+
58
+ def credentials_problem(key, keys)
59
+ return "~/.gem/credentials has no keys at all (missing, empty, or not mounted)." if keys.empty?
60
+ return "configured key '#{key}' is not among ~/.gem/credentials' keys #{keys.inspect}." if key && !keys.include?(key)
61
+ return if key || keys.include?("rubygems_api_key")
62
+
63
+ "no gem.push_key configured and ~/.gem/credentials has no default 'rubygems_api_key' key (found: #{keys.inspect})."
64
+ end
65
+ end
66
+
67
+ desc "Show which rubygems.org credential key `rake release`/`gem push` will use"
68
+ task :release_credentials_check do
69
+ # Without this, stdout is buffered (not a TTY once piped/captured) while stderr isn't - so
70
+ # `abort`'s message below can reach the terminal before the `puts` lines that logically ran
71
+ # first, once the two streams get merged. Confirmed live: without sync, "release aborted: ..."
72
+ # printed above the diagnostic banner, not after it.
73
+ $stdout.sync = true
74
+
75
+ key = ReleaseCredentialsCheck.effective_key
76
+ keys = ReleaseCredentialsCheck.available_keys
77
+ ReleaseCredentialsCheck.print_banner(key, keys)
78
+
79
+ problem = ReleaseCredentialsCheck.problem(key, keys)
80
+ abort "release aborted: #{problem}" if problem
81
+
82
+ puts "NOTE: no gem.push_key configured - this will push using the default :rubygems_api_key." if key.nil?
83
+ end
84
+
85
+ # A prerequisite of "release:rubygem_push" specifically, not the outer "release" task. Bundler's
86
+ # own gem_helper.rb defines release as depending on
87
+ # ["build", "release:guard_clean", "release:source_control_push", "release:rubygem_push"], run in
88
+ # that listed order, with release:rubygem_push's own action being the actual `rubygem_push(...)`
89
+ # call that consumes this credential - attaching to the outer "release" task instead would append
90
+ # here via Task#enhance's simple union, running this *after* every one of those, including the
91
+ # push itself, which would make it a report on a mistake already made, not a guard against one.
92
+ # Attaching to "release:rubygem_push" guarantees Rake resolves this before *that* task's own
93
+ # action runs, regardless of how bundler orders its other prerequisites in some other version -
94
+ # automatically, regardless of how `rake release` gets invoked (this Makefile's release-shell, a
95
+ # bare host checkout, CI), not something you have to remember to check with a separate command.
96
+ desc "Push the built gem to rubygems.org (checks credentials first)"
97
+ task "release:rubygem_push" => :release_credentials_check