Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: main
fetch-depth: 0

- name: Check for changesets
id: check
Expand Down Expand Up @@ -86,7 +85,6 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: main
fetch-depth: 0
token: ${{ steps.releaser.outputs.token }}

- name: Configure Git
Expand Down Expand Up @@ -202,7 +200,6 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: main
fetch-depth: 0

- name: Configure Git
run: |
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ jobs:
- name: Run RuboCop
run: bundle exec rubocop

- name: Check public API snapshot
run: bundle exec rake public_api:check

gem-build:
name: Gem build
runs-on: ubuntu-latest
Expand Down
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ Run the same checks CI uses before opening a PR:
```bash
bundle exec rspec
bundle exec rubocop
bundle exec rake public_api:check
```

If you intentionally change the public Ruby API, update the snapshot and review the diff:

```bash
bundle exec rake public_api:generate
```

## Rails package
Expand Down
15 changes: 15 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require_relative 'scripts/public_api_snapshot'

namespace :public_api do
desc 'Generate the public API snapshot'
task :generate do
PublicApiSnapshot.write
end

desc 'Check that the public API snapshot matches the current code'
task :check do
exit 1 unless PublicApiSnapshot.check
end
end
119 changes: 119 additions & 0 deletions public_api_snapshot.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# This file is generated by `bundle exec rake public_api:generate`.
# Run `bundle exec rake public_api:check` to detect local public API changes.

module PostHog
class PostHog::Client
instance_method PostHog::Client#alias(attrs)
instance_method PostHog::Client#capture(attrs)
instance_method PostHog::Client#capture_exception(exception, distinct_id = ..., additional_properties = ..., flags: ...)
instance_method PostHog::Client#clear()
instance_method PostHog::Client#dequeue_last_message()
instance_method PostHog::Client#evaluate_flags(distinct_id, groups: ..., person_properties: ..., group_properties: ..., only_evaluate_locally: ..., disable_geoip: ..., flag_keys: ...)
instance_method PostHog::Client#flush()
instance_method PostHog::Client#get_all_flags(distinct_id, groups: ..., person_properties: ..., group_properties: ..., only_evaluate_locally: ...)
instance_method PostHog::Client#get_all_flags_and_payloads(distinct_id, groups: ..., person_properties: ..., group_properties: ..., only_evaluate_locally: ...)
instance_method PostHog::Client#get_feature_flag(key, distinct_id, groups: ..., person_properties: ..., group_properties: ..., only_evaluate_locally: ..., send_feature_flag_events: ...)
instance_method PostHog::Client#get_feature_flag_payload(key, distinct_id, match_value: ..., groups: ..., person_properties: ..., group_properties: ..., only_evaluate_locally: ...)
instance_method PostHog::Client#get_feature_flag_result(key, distinct_id, groups: ..., person_properties: ..., group_properties: ..., only_evaluate_locally: ..., send_feature_flag_events: ...)
instance_method PostHog::Client#get_remote_config_payload(flag_key)
instance_method PostHog::Client#group_identify(attrs)
instance_method PostHog::Client#identify(attrs)
instance_method PostHog::Client#is_feature_enabled(flag_key, distinct_id, groups: ..., person_properties: ..., group_properties: ..., only_evaluate_locally: ..., send_feature_flag_events: ...)
instance_method PostHog::Client#queued_messages()
instance_method PostHog::Client#reload_feature_flags()
instance_method PostHog::Client#shutdown()
class_method PostHog::Client.logger()
class_method PostHog::Client.reset_instance_tracking!()
module PostHog::Defaults
module PostHog::Defaults::BackoffPolicy
constant PostHog::Defaults::BackoffPolicy::MAX_TIMEOUT_MS: Integer
constant PostHog::Defaults::BackoffPolicy::MIN_TIMEOUT_MS: Integer
constant PostHog::Defaults::BackoffPolicy::MULTIPLIER: Float
constant PostHog::Defaults::BackoffPolicy::RANDOMIZATION_FACTOR: Float
module PostHog::Defaults::FeatureFlags
constant PostHog::Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS: Integer
constant PostHog::Defaults::MAX_HASH_SIZE: Integer
module PostHog::Defaults::Message
constant PostHog::Defaults::Message::MAX_BYTES: Integer
module PostHog::Defaults::MessageBatch
constant PostHog::Defaults::MessageBatch::MAX_BYTES: Integer
constant PostHog::Defaults::MessageBatch::MAX_SIZE: Integer
module PostHog::Defaults::Queue
constant PostHog::Defaults::Queue::MAX_SIZE: Integer
module PostHog::Defaults::Request
constant PostHog::Defaults::Request::HEADERS: Hash
constant PostHog::Defaults::Request::HOST: String
constant PostHog::Defaults::Request::PATH: String
constant PostHog::Defaults::Request::PORT: Integer
constant PostHog::Defaults::Request::RETRIES: Integer
constant PostHog::Defaults::Request::SSL: Boolean
class PostHog::FeatureFlagEvaluations
instance_method PostHog::FeatureFlagEvaluations#distinct_id()
instance_method PostHog::FeatureFlagEvaluations#enabled?(key)
instance_method PostHog::FeatureFlagEvaluations#evaluated_at()
instance_method PostHog::FeatureFlagEvaluations#flag_definitions_loaded_at()
instance_method PostHog::FeatureFlagEvaluations#get_flag(key)
instance_method PostHog::FeatureFlagEvaluations#get_flag_payload(key)
instance_method PostHog::FeatureFlagEvaluations#groups()
instance_method PostHog::FeatureFlagEvaluations#keys()
instance_method PostHog::FeatureFlagEvaluations#only(keys)
instance_method PostHog::FeatureFlagEvaluations#only_accessed()
instance_method PostHog::FeatureFlagEvaluations#request_id()
constant PostHog::FeatureFlagEvaluations::EVALUATED_LOCALLY_REASON: String
class PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord < Struct
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#enabled()
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#enabled=(_)
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#id()
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#id=(_)
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#key()
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#key=(_)
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#locally_evaluated()
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#locally_evaluated=(_)
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#payload()
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#payload=(_)
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#reason()
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#reason=(_)
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#variant()
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#variant=(_)
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#version()
instance_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord#version=(_)
class_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord.[](*arg0)
class_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord.inspect()
class_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord.keyword_init?()
class_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord.members()
class_method PostHog::FeatureFlagEvaluations::EvaluatedFlagRecord.new(*arg0)
class PostHog::FeatureFlagEvaluations::Host < Struct
instance_method PostHog::FeatureFlagEvaluations::Host#capture_flag_called_event_if_needed()
instance_method PostHog::FeatureFlagEvaluations::Host#capture_flag_called_event_if_needed=(_)
instance_method PostHog::FeatureFlagEvaluations::Host#log_warning()
instance_method PostHog::FeatureFlagEvaluations::Host#log_warning=(_)
class_method PostHog::FeatureFlagEvaluations::Host.[](*arg0)
class_method PostHog::FeatureFlagEvaluations::Host.inspect()
class_method PostHog::FeatureFlagEvaluations::Host.keyword_init?()
class_method PostHog::FeatureFlagEvaluations::Host.members()
class_method PostHog::FeatureFlagEvaluations::Host.new(*arg0)
class PostHog::FeatureFlagResult
instance_method PostHog::FeatureFlagResult#enabled?()
instance_method PostHog::FeatureFlagResult#key()
instance_method PostHog::FeatureFlagResult#payload()
instance_method PostHog::FeatureFlagResult#value()
instance_method PostHog::FeatureFlagResult#variant()
class_method PostHog::FeatureFlagResult.from_value_and_payload(key, value, payload)
class_method PostHog::FeatureFlagResult.parse_payload(payload)
module PostHog::FlagDefinitionCacheProvider
class_method PostHog::FlagDefinitionCacheProvider.validate!(provider)
constant PostHog::FlagDefinitionCacheProvider::REQUIRED_METHODS: Array
class PostHog::InconclusiveMatchError < StandardError
module PostHog::Logging
instance_method PostHog::Logging#logger()
class_method PostHog::Logging.included(base)
class_method PostHog::Logging.logger()
class_method PostHog::Logging.logger=(arg0)
class PostHog::RequiresServerEvaluation < StandardError
class PostHog::SendFeatureFlagsOptions
instance_method PostHog::SendFeatureFlagsOptions#group_properties()
instance_method PostHog::SendFeatureFlagsOptions#only_evaluate_locally()
instance_method PostHog::SendFeatureFlagsOptions#person_properties()
instance_method PostHog::SendFeatureFlagsOptions#to_h()
class_method PostHog::SendFeatureFlagsOptions.from_hash(hash)
constant PostHog::VERSION: String
215 changes: 215 additions & 0 deletions scripts/public_api_snapshot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# frozen_string_literal: true

require 'open3'
require 'tempfile'

module PublicApiSnapshot
ROOT = File.expand_path('..', __dir__).freeze
LIB_DIR = File.join(ROOT, 'lib').freeze
SNAPSHOT_PATH = File.join(ROOT, 'public_api_snapshot.txt').freeze
HEADER = <<~HEADER
# This file is generated by `bundle exec rake public_api:generate`.
# Run `bundle exec rake public_api:check` to detect local public API changes.
HEADER

module_function

def generate
load_sdk

entries = []
append_constant(entries, Object, :PostHog, 'PostHog')

"#{HEADER}\n#{entries.join("\n")}\n"
end

def write
File.write(SNAPSHOT_PATH, generate)
puts "Updated #{relative_path(SNAPSHOT_PATH)}"
end

def check
expected = File.exist?(SNAPSHOT_PATH) ? File.read(SNAPSHOT_PATH) : nil
actual = generate

return true if expected == actual

warn 'Public API snapshot is out of date. Run `bundle exec rake public_api:generate` and review the diff.'
print_diff(expected, actual)
false
end

def load_sdk
$LOAD_PATH.unshift(LIB_DIR) unless $LOAD_PATH.include?(LIB_DIR)
require 'posthog'
end

def append_constant(entries, parent, constant_name, qualified_name)
return if private_constant?(parent, constant_name, qualified_name)

value = parent.const_get(constant_name, false)
return unless value.is_a?(Module)

entries << constant_entry(value, qualified_name)
append_methods(entries, value, qualified_name)
append_nested_constants(entries, value, qualified_name)
end

def append_nested_constants(entries, mod, qualified_name)
mod.constants(false).sort_by(&:to_s).each do |constant_name|
append_literal_constant(entries, mod, constant_name, qualified_name)
append_constant(entries, mod, constant_name, "#{qualified_name}::#{constant_name}")
end
end

def append_literal_constant(entries, mod, constant_name, qualified_name)
return if private_constant?(mod, constant_name, "#{qualified_name}::#{constant_name}")

value = mod.const_get(constant_name, false)
return if value.is_a?(Module)

entries << "constant #{qualified_name}::#{constant_name}: #{constant_type(value)}"
end

def append_methods(entries, mod, qualified_name)
public_instance_methods(mod).each do |method_name|
next if private_api_method?(mod.instance_method(method_name), method_name)

entries << "instance_method #{qualified_name}##{method_name}#{method_signature(mod.instance_method(method_name))}"
end

public_singleton_methods(mod).each do |method_name|
method = mod.method(method_name)
next if private_api_method?(method, method_name)

entries << "class_method #{qualified_name}.#{method_name}#{method_signature(method)}"
end
end

def constant_entry(value, qualified_name)
case value
when Class
superclass = value.superclass
suffix = superclass && superclass != Object ? " < #{superclass.name}" : ''
"class #{qualified_name}#{suffix}"
else
"module #{qualified_name}"
end
end

def public_instance_methods(mod)
mod.public_instance_methods(false).sort_by(&:to_s)
end

def public_singleton_methods(mod)
mod.singleton_class.public_instance_methods(false).sort_by(&:to_s)
end

def private_constant?(parent, constant_name, qualified_name)
qualified_name.start_with?('PostHog::Internal') ||
source_marked_private?(parent.const_source_location(constant_name, false))
end

def private_api_method?(method, method_name)
method_name.to_s.start_with?('_') || source_marked_private?(method.source_location)
end

def source_marked_private?(location)
return false unless location

file, line = location
return false unless file&.start_with?(LIB_DIR) && File.file?(file)

preceding_comment(file, line).include?('@api private')
end

def preceding_comment(file, line)
lines = File.readlines(file)
index = line - 2
comment = []

while index >= 0
text = lines[index]
break unless text.match?(/^\s*#/) || text.match?(/^\s*$/)

comment.unshift(text)
index -= 1
end

comment.join
end

def constant_type(value)
return 'Boolean' if [true, false].include?(value)
return 'nil' if value.nil?

value.class.name
end

def method_signature(method)
params = method.parameters.map.with_index do |(type, name), index|
format_parameter(type, name, index)
end
"(#{params.join(', ')})"
end

def format_parameter(type, name, index)
name ||= "arg#{index}"

case type
when :req
name.to_s
when :opt
"#{name} = ..."
when :rest
"*#{name}"
when :keyreq
"#{name}:"
when :key
"#{name}: ..."
when :keyrest
"**#{name}"
when :block
"&#{name}"
else
"#{type}:#{name}"
end
end

def print_diff(expected, actual)
Tempfile.create('public-api-expected') do |expected_file|
Tempfile.create('public-api-actual') do |actual_file|
expected_file.write(expected || '')
actual_file.write(actual)
expected_file.flush
actual_file.flush

diff_output, stderr, status = Open3.capture3(
'diff', '-u', '--label', relative_path(SNAPSHOT_PATH), expected_file.path,
'--label', 'generated public API', actual_file.path
)
warn stderr unless stderr.empty?
puts diff_output unless diff_output.empty?
warn 'No diff output available.' unless status.exitstatus == 1 || !diff_output.empty?
end
end
rescue Errno::ENOENT
warn 'Install diff or run `bundle exec rake public_api:generate` to update the snapshot.'
end

def relative_path(path)
path.delete_prefix("#{ROOT}/")
end
end

if $PROGRAM_NAME == __FILE__
case ARGV.first
when '--check'
exit(PublicApiSnapshot.check ? 0 : 1)
when '--generate', nil
PublicApiSnapshot.write
else
warn 'Usage: ruby scripts/public_api_snapshot.rb [--check|--generate]'
exit 2
end
end
Loading