better_auth-sinatra 0.8.0 → 0.10.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: 7c7447781067976bc4c48c8a826db7f10ea2a1b730da8d0ffe1a084e22c97357
4
- data.tar.gz: 6471dbc15a2cd08755db616741081cc8aee613040153512796e86e1ce0df300f
3
+ metadata.gz: c1354b443fe041ddaa63d80da87d5386dd88128ff7b0fdce339b4ec123751e4e
4
+ data.tar.gz: 38c93a00515742217abe673835752acc65465e6c207904d1219c605f94409ad4
5
5
  SHA512:
6
- metadata.gz: '09a9f4b4d5b0ef1491e1a20c653cf3a3aaa3a317f65a478b3f4c78d764be46d92faddf6c2c3a9690923fb84aced0b6d6e049496fb40f33309baed358200779d6'
7
- data.tar.gz: 67e80a63f8e8db1564265a4f74581cb96c45a5af0bcf9283c27f769685f29a648dc62b8e51ade56bcd3dce3d84fc24dfadba748a5a05abfb6a0ccc81b5791cc4
6
+ metadata.gz: 674b03f0268f4ed4b07f4d66c0ac348645d3506a7787e0593cd1e33ffdc8a20c5b045e701c998621c7d15ce006c520ec07cc538c4cbfe03fd03263b41b63eaae
7
+ data.tar.gz: 4c13b0fdec4ec941beef0786b353abaee4b9ec56d5a952af91414e0a65e108f4adb7ea6b8c1801024ad66da789cb5e978b319eb7bc99a445ff0f962b8db980e2
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.10.0 - 2026-05-21
6
+
7
+ - Removed obsolete helper wiring and expanded mounted app, migration, and routing coverage.
8
+ - Updated Sinatra docs for current mounting and integration behavior.
9
+
5
10
  ## 0.7.0 - 2026-05-05
6
11
 
7
12
  - Fixed auth dispatch when Rack splits mounted paths across `SCRIPT_NAME` and `PATH_INFO`.
data/README.md CHANGED
@@ -44,6 +44,10 @@ cannot be `/`, because that would capture every Sinatra route before the app can
44
44
  handle it. The core app still owns routes such as `/ok`, `/sign-up/email`,
45
45
  `/sign-in/email`, and plugin endpoints.
46
46
 
47
+ Call `better_auth` once per Sinatra app class. Registering it more than once is
48
+ treated as a configuration error because each Rack mount must delegate to one
49
+ core Better Auth instance.
50
+
47
51
  `better_auth at:` sets the path prefix that Better Auth uses as its core
48
52
  `base_path`. The adapter supports two common Rack mount patterns:
49
53
 
@@ -58,7 +62,10 @@ When using reverse proxies, `Rack::URLMap`, or another parent app, make sure the
58
62
  `PATH_INFO` visible to Sinatra still aligns with the configured auth prefix.
59
63
  `SCRIPT_NAME` handling depends on the Rack server and mount stack, so verify
60
64
  redirect URLs and cookie paths in integration tests when mounting below a
61
- sub-path.
65
+ sub-path. If the public auth URL includes a parent mount prefix, set
66
+ `config.base_url` to that public URL, for example `https://app.example/api/auth`;
67
+ core URL inference uses the configured Better Auth base path and cannot infer
68
+ every parent Rack mount layout from Sinatra alone.
62
69
 
63
70
  ## Helpers
64
71
 
@@ -91,11 +98,27 @@ Available tasks:
91
98
  rake better_auth:install
92
99
  rake better_auth:generate:migration
93
100
  rake better_auth:migrate
101
+ rake better_auth:migrate:status
102
+ rake better_auth:doctor
94
103
  rake better_auth:routes
95
104
  ```
96
105
 
97
106
  `better_auth:install` creates `config/better_auth.rb`. SQL migrations are
98
- generated under `db/better_auth/migrate`.
107
+ generated under `db/better_auth/migrate`. When a SQL adapter is configured,
108
+ generation introspects the current database and emits only missing Better Auth
109
+ tables, columns, and indexes.
110
+
111
+ Migration and route tasks load Better Auth configuration from
112
+ `config/better_auth.rb` by default. Set `BETTER_AUTH_CONFIG` (or the
113
+ OpenAuth-compatible `OPEN_AUTH_CONFIG`) to a shared config file if your app keeps
114
+ Better Auth setup elsewhere:
115
+
116
+ ```bash
117
+ BETTER_AUTH_CONFIG=config/auth/better_auth.rb rake better_auth:generate:migration
118
+ ```
119
+
120
+ The migration tasks fail when no config file is found, so generated SQL cannot
121
+ silently omit app plugins or custom schema options.
99
122
 
100
123
  ## Database Notes
101
124
 
@@ -103,10 +126,14 @@ Sinatra does not include a Rails-style database layer or migration command.
103
126
  This adapter uses Better Auth core SQL adapters for migrations. Set
104
127
  `BETTER_AUTH_DIALECT=postgres`, `mysql`, or `sqlite` when generating SQL.
105
128
 
106
- Generated SQL should keep one statement per line ending with `;`. The migration
107
- runner handles simple single-line multi-statement files, but hand-edited SQL
108
- with semicolons inside string literals can confuse the splitter. DDL rollback
109
- behavior depends on the database, so back up production data before migrating.
129
+ The migration runner delegates SQL rendering and execution behavior to the core
130
+ Better Auth SQL migration layer. It handles multiple statements, quoted strings,
131
+ and PostgreSQL dollar-quoted blocks; DDL rollback behavior still depends on the
132
+ database, so back up production data before migrating.
133
+
134
+ Exhaustive adapter behavior for PostgreSQL, MySQL, SQLite, and other database
135
+ families is covered in the core `better_auth` package. This Sinatra package only
136
+ smoke-tests that its configuration and Rake tasks delegate to those core paths.
110
137
 
111
138
  ActiveRecord-backed Sinatra migrations are not supported yet. Apps that already
112
139
  use `sinatra-activerecord` can still configure Better Auth manually, but the v1
@@ -16,7 +16,7 @@ module BetterAuth
16
16
  "better_auth mount path cannot be '/' (it would capture every request). " \
17
17
  "Use a prefix such as #{BetterAuth::Configuration::DEFAULT_BASE_PATH.inspect}."
18
18
  end
19
- warn "[better_auth-sinatra] better_auth is already configured for this app; the new configuration will be appended." if respond_to?(:better_auth_auth)
19
+ raise ArgumentError, "better_auth is already configured for this app" if respond_to?(:better_auth_auth)
20
20
 
21
21
  config = BetterAuth::Sinatra.configuration.copy
22
22
  yield config if block_given?
@@ -54,26 +54,34 @@ module BetterAuth
54
54
  def resolve_better_auth_session
55
55
  auth = better_auth_auth
56
56
  result = auth.api.get_session(
57
- headers: better_auth_request_headers,
58
- return_headers: true
57
+ request: Rack::Request.new(request.env),
58
+ method: "GET",
59
+ as_response: true
59
60
  )
61
+ return resolve_better_auth_response(result) if result.respond_to?(:headers) && result.respond_to?(:body)
62
+
60
63
  apply_better_auth_response_headers(result[:headers] || result["headers"] || {})
61
64
  result[:response] || result["response"]
62
65
  end
63
66
 
64
- def better_auth_request_headers
65
- request.env.each_with_object({}) do |(key, value), headers|
66
- case key
67
- when "CONTENT_TYPE"
68
- headers["content-type"] = value if value
69
- when "CONTENT_LENGTH"
70
- headers["content-length"] = value if value
71
- else
72
- next unless key.start_with?("HTTP_")
73
-
74
- headers[key.delete_prefix("HTTP_").downcase.tr("_", "-")] = value
75
- end
76
- end
67
+ def resolve_better_auth_response(response)
68
+ apply_better_auth_response_headers(response.headers || {})
69
+ body = response.body.respond_to?(:join) ? response.body.join : response.body.to_s
70
+ payload = body.empty? ? nil : JSON.parse(body)
71
+ raise_better_auth_response_error(response, payload) if response.status.to_i >= 400
72
+
73
+ payload
74
+ end
75
+
76
+ def raise_better_auth_response_error(response, payload)
77
+ payload = payload.is_a?(Hash) ? payload : {}
78
+ status = BetterAuth::APIError::STATUS_CODES.key(response.status.to_i) || "INTERNAL_SERVER_ERROR"
79
+ raise BetterAuth::APIError.new(
80
+ status,
81
+ message: payload["message"],
82
+ code: payload["code"],
83
+ headers: response.headers || {}
84
+ )
77
85
  end
78
86
 
79
87
  def apply_better_auth_response_headers(headers)
@@ -1,250 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fileutils"
3
+ require "better_auth/sql_migration"
4
4
 
5
5
  module BetterAuth
6
6
  module Sinatra
7
7
  module Migration
8
- DEFAULT_MIGRATIONS_PATH = "db/better_auth/migrate"
9
- MISSING_MIGRATIONS_TABLE_MESSAGES = [
10
- /no such table/i,
11
- /relation .* does not exist/i,
12
- /table .* doesn't exist/i,
13
- /undefined table/i,
14
- /invalid object name/i
15
- ].freeze
16
-
17
- class UnsupportedAdapterError < StandardError; end
8
+ DEFAULT_MIGRATIONS_PATH = BetterAuth::SQLMigration::DEFAULT_MIGRATIONS_PATH
9
+ MISSING_MIGRATIONS_TABLE_MESSAGES = BetterAuth::SQLMigration::MISSING_MIGRATIONS_TABLE_MESSAGES
10
+ UnsupportedAdapterError = BetterAuth::SQLMigration::UnsupportedAdapterError
11
+ GENERATOR = "better_auth-sinatra"
18
12
 
19
13
  module_function
20
14
 
21
15
  def render(options, dialect:)
22
- dialect = normalize_dialect(dialect)
23
- config = configuration_for(options)
24
- statements = BetterAuth::Schema::SQL.create_statements(config, dialect: dialect)
25
- [
26
- "-- Generated by better_auth-sinatra",
27
- "-- Dialect: #{dialect}",
28
- "",
29
- statements.join("\n\n"),
30
- ""
31
- ].join("\n")
16
+ BetterAuth::SQLMigration.render(options, dialect: dialect, generator: GENERATOR)
32
17
  end
33
18
 
34
- def generate(options, dialect:, migrations_path: DEFAULT_MIGRATIONS_PATH, timestamp: Time.now.utc.strftime("%Y%m%d%H%M%S"))
35
- dialect = normalize_dialect(dialect)
36
- FileUtils.mkdir_p(migrations_path)
37
- path = File.join(migrations_path, "#{timestamp}_create_better_auth_tables.sql")
38
- return path if File.exist?(path)
39
-
40
- File.write(path, render(options, dialect: dialect))
41
- path
19
+ def generate(options, dialect:, migrations_path: DEFAULT_MIGRATIONS_PATH, timestamp: Time.now.utc.strftime("%Y%m%d%H%M%S"), connection: nil)
20
+ BetterAuth::SQLMigration.generate(
21
+ options,
22
+ dialect: dialect,
23
+ generator: GENERATOR,
24
+ migrations_path: migrations_path,
25
+ timestamp: timestamp,
26
+ connection: connection
27
+ )
42
28
  end
43
29
 
44
30
  def migrate(auth_or_options, migrations_path: DEFAULT_MIGRATIONS_PATH)
45
- auth = auth_for(auth_or_options)
46
- adapter = auth.context.adapter
47
- unless adapter.respond_to?(:dialect) && adapter.respond_to?(:connection)
48
- raise UnsupportedAdapterError, "better_auth-sinatra migrations require core SQL adapters with connection and dialect support"
49
- end
50
-
51
- connection = adapter.connection
52
- dialect = normalize_dialect(adapter.dialect)
53
- files = Dir[File.join(migrations_path, "*.sql")].sort
54
- ensure_schema_migrations!(connection, dialect)
55
- applied = applied_migrations(connection, dialect)
56
-
57
- files.reject { |file| applied.include?(File.basename(file)) }.each do |file|
58
- execute_sql(connection, File.read(file))
59
- record_migration(connection, dialect, File.basename(file))
60
- end
61
- end
62
-
63
- def configuration_for(options)
64
- return options.options if options.is_a?(BetterAuth::Auth)
65
- return options if options.is_a?(BetterAuth::Configuration)
66
-
67
- BetterAuth::Configuration.new(options)
68
- end
69
-
70
- def auth_for(value)
71
- return value if value.is_a?(BetterAuth::Auth)
72
-
73
- BetterAuth.auth(value)
74
- end
75
-
76
- def ensure_schema_migrations!(connection, dialect)
77
- sql = case dialect
78
- when :postgres, :sqlite
79
- %(CREATE TABLE IF NOT EXISTS #{quote("better_auth_schema_migrations", dialect)} (#{quote("version", dialect)} text PRIMARY KEY);)
80
- when :mysql
81
- %(CREATE TABLE IF NOT EXISTS #{quote("better_auth_schema_migrations", dialect)} (#{quote("version", dialect)} varchar(191) PRIMARY KEY) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;)
82
- when :mssql
83
- %(IF OBJECT_ID(N'#{quote("better_auth_schema_migrations", dialect)}', N'U') IS NULL CREATE TABLE #{quote("better_auth_schema_migrations", dialect)} (#{quote("version", dialect)} varchar(255) PRIMARY KEY);)
84
- else
85
- raise UnsupportedAdapterError, "Unsupported SQL dialect for better_auth-sinatra migrations: #{dialect}"
86
- end
87
- execute_sql(connection, sql)
31
+ BetterAuth::SQLMigration.migrate(auth_or_options, migrations_path: migrations_path)
88
32
  end
89
33
 
90
- def applied_migrations(connection, dialect)
91
- rows = execute_sql(connection, "SELECT #{quote("version", dialect)} FROM #{quote("better_auth_schema_migrations", dialect)};")
92
- Array(rows).map { |row| row["version"] || row[:version] }
93
- rescue UnsupportedAdapterError
94
- raise
95
- rescue => error
96
- raise error unless missing_schema_migrations_table?(error)
97
-
98
- []
99
- end
100
-
101
- def record_migration(connection, dialect, version)
102
- sql = "INSERT INTO #{quote("better_auth_schema_migrations", dialect)} (#{quote("version", dialect)}) VALUES (#{literal(version)});"
103
- execute_sql(connection, sql)
104
- end
105
-
106
- def execute_sql(connection, sql)
107
- statements(sql).each_with_object([]) do |statement, results|
108
- result =
109
- if connection.respond_to?(:exec)
110
- connection.exec(statement)
111
- elsif connection.respond_to?(:execute)
112
- connection.execute(statement)
113
- elsif connection.respond_to?(:query)
114
- connection.query(statement)
115
- else
116
- raise UnsupportedAdapterError, "SQL connection does not support exec, execute, or query"
117
- end
118
- results.concat(result.to_a) if result.respond_to?(:to_a)
119
- end
120
- end
121
-
122
- def statements(sql)
123
- normalized = sql.to_s.gsub("\r\n", "\n").strip
124
- return [] if normalized.empty?
125
-
126
- split_sql_statements(normalized)
127
- end
128
-
129
- def split_sql_statements(sql)
130
- output = []
131
- buffer = +""
132
- index = 0
133
- quote = nil
134
- line_comment = false
135
- block_comment = false
136
- dollar_tag = nil
137
-
138
- while index < sql.length
139
- state = {
140
- quote: quote,
141
- line_comment: line_comment,
142
- block_comment: block_comment,
143
- dollar_tag: dollar_tag
144
- }
145
- index, quote, line_comment, block_comment, dollar_tag = scan_sql_character(sql, buffer, index, state, output)
146
- end
147
-
148
- tail = buffer.strip
149
- output << tail unless tail.empty?
150
- output
151
- end
152
-
153
- def scan_sql_character(sql, buffer, index, state, output)
154
- char = sql[index]
155
- next_char = sql[index + 1]
156
- quote = state[:quote]
157
- line_comment = state[:line_comment]
158
- block_comment = state[:block_comment]
159
- dollar_tag = state[:dollar_tag]
160
-
161
- return scan_line_comment(buffer, index, char) + [quote, false, block_comment, dollar_tag] if line_comment && char == "\n"
162
- return scan_line_comment(buffer, index, char) + [quote, true, block_comment, dollar_tag] if line_comment
163
- return scan_block_comment(buffer, index, char, next_char, quote, line_comment, dollar_tag) if block_comment
164
- return scan_dollar_quote(sql, buffer, index, char, quote, line_comment, block_comment, dollar_tag) if dollar_tag
165
- return scan_quoted_string(sql, buffer, index, char, quote, line_comment, block_comment, dollar_tag) if quote
166
- return [index + 2, quote, true, block_comment, dollar_tag].tap { buffer << char << next_char } if char == "-" && next_char == "-"
167
- return [index + 2, quote, line_comment, true, dollar_tag].tap { buffer << char << next_char } if char == "/" && next_char == "*"
168
-
169
- tag = dollar_quote_tag_at(sql, index)
170
- return [index + tag.length, quote, line_comment, block_comment, tag].tap { buffer << tag } if tag
171
- return [index + 1, char, line_comment, block_comment, dollar_tag].tap { buffer << char } if char == "'" || char == "\""
172
-
173
- if char == ";"
174
- statement = buffer.strip
175
- output << statement unless statement.empty?
176
- buffer.clear
177
- return [index + 1, quote, line_comment, block_comment, dollar_tag]
178
- end
179
-
180
- buffer << char
181
- [index + 1, quote, line_comment, block_comment, dollar_tag]
182
- end
183
-
184
- def scan_line_comment(buffer, index, char)
185
- buffer << char
186
- [index + 1]
187
- end
188
-
189
- def scan_block_comment(buffer, index, char, next_char, quote, line_comment, dollar_tag)
190
- buffer << char
191
- if char == "*" && next_char == "/"
192
- buffer << next_char
193
- [index + 2, quote, line_comment, false, dollar_tag]
194
- else
195
- [index + 1, quote, line_comment, true, dollar_tag]
196
- end
197
- end
198
-
199
- def scan_dollar_quote(sql, buffer, index, char, quote, line_comment, block_comment, dollar_tag)
200
- if sql[index, dollar_tag.length] == dollar_tag
201
- buffer << dollar_tag
202
- [index + dollar_tag.length, quote, line_comment, block_comment, nil]
203
- else
204
- buffer << char
205
- [index + 1, quote, line_comment, block_comment, dollar_tag]
206
- end
207
- end
208
-
209
- def scan_quoted_string(sql, buffer, index, char, quote, line_comment, block_comment, dollar_tag)
210
- buffer << char
211
- if char == quote && sql[index + 1] == quote
212
- buffer << sql[index + 1]
213
- [index + 2, quote, line_comment, block_comment, dollar_tag]
214
- elsif char == quote
215
- [index + 1, nil, line_comment, block_comment, dollar_tag]
216
- else
217
- [index + 1, quote, line_comment, block_comment, dollar_tag]
218
- end
219
- end
220
-
221
- def dollar_quote_tag_at(sql, index)
222
- match = sql[index..]&.match(/\A\$[A-Za-z_][A-Za-z0-9_]*\$|\A\$\$/)
223
- match&.[](0)
224
- end
225
-
226
- def missing_schema_migrations_table?(error)
227
- message = error.message.to_s
228
- MISSING_MIGRATIONS_TABLE_MESSAGES.any? { |pattern| message.match?(pattern) }
229
- end
230
-
231
- def quote(identifier, dialect)
232
- BetterAuth::Schema::SQL.quote(identifier, dialect)
233
- end
34
+ def method_missing(name, *args, **kwargs, &block)
35
+ return BetterAuth::SQLMigration.public_send(name, *args, **kwargs, &block) if BetterAuth::SQLMigration.respond_to?(name)
234
36
 
235
- def literal(value)
236
- "'#{value.to_s.gsub("'", "''")}'"
37
+ super
237
38
  end
238
39
 
239
- def normalize_dialect(value)
240
- case value.to_s.downcase
241
- when "postgresql"
242
- :postgres
243
- when "sqlite3"
244
- :sqlite
245
- else
246
- value.to_sym
247
- end
40
+ def respond_to_missing?(name, include_private = false)
41
+ BetterAuth::SQLMigration.respond_to?(name, include_private) || super
248
42
  end
249
43
  end
250
44
  end
@@ -29,7 +29,7 @@ module BetterAuth
29
29
  def mount_matches?(env)
30
30
  return false if @mount_path == "/"
31
31
 
32
- path_info = normalize_path(env["PATH_INFO"])
32
+ path_info = normalize_path(env["PATH_INFO"], trim: true)
33
33
  return true if path_info == @mount_path || path_info.start_with?("#{@mount_path}/")
34
34
 
35
35
  full = full_request_path(env)
@@ -39,33 +39,34 @@ module BetterAuth
39
39
  def full_request_path(env)
40
40
  script = env.fetch("SCRIPT_NAME", "").to_s
41
41
  path = env.fetch("PATH_INFO", "").to_s
42
- normalize_path("#{script}#{path}")
42
+ normalize_path("#{script}#{path}", trim: true)
43
43
  end
44
44
 
45
45
  def mounted_path_info(env)
46
- path_info = normalize_path(env["PATH_INFO"])
47
- return path_info if path_info == @mount_path || path_info.start_with?("#{@mount_path}/")
46
+ path_info = normalize_path(env["PATH_INFO"], trim: false)
47
+ comparable_path = normalize_path(env["PATH_INFO"], trim: true)
48
+ return path_info if comparable_path == @mount_path || comparable_path.start_with?("#{@mount_path}/")
48
49
 
49
- script_name = normalize_path(env["SCRIPT_NAME"])
50
+ script_name = normalize_path(env["SCRIPT_NAME"], trim: true)
50
51
  prefix = (script_name == "/") ? @mount_path : script_name
51
- return path_info if path_info == prefix || path_info.start_with?("#{prefix}/")
52
+ return path_info if comparable_path == prefix || comparable_path.start_with?("#{prefix}/")
52
53
 
53
- normalize_path("#{prefix}/#{path_info.delete_prefix("/")}")
54
+ normalize_path("#{prefix}/#{path_info.delete_prefix("/")}", trim: false)
54
55
  end
55
56
 
56
57
  def shared_mount_rewrite?(env, rewritten_path)
57
- script_name = normalize_path(env["SCRIPT_NAME"])
58
- original_path = normalize_path(env["PATH_INFO"])
58
+ script_name = normalize_path(env["SCRIPT_NAME"], trim: true)
59
+ original_path = normalize_path(env["PATH_INFO"], trim: true)
59
60
  script_name != "/" &&
60
61
  !original_path.start_with?("#{@mount_path}/") &&
61
62
  rewritten_path.start_with?("#{@mount_path}/")
62
63
  end
63
64
 
64
- def normalize_path(path)
65
+ def normalize_path(path, trim: true)
65
66
  normalized = path.to_s
66
67
  normalized = "/#{normalized}" unless normalized.start_with?("/")
67
68
  normalized = normalized.squeeze("/")
68
- normalized = normalized.delete_suffix("/") unless normalized == "/"
69
+ normalized = normalized.delete_suffix("/") if trim && normalized != "/"
69
70
  normalized.empty? ? "/" : normalized
70
71
  end
71
72
  end
@@ -22,23 +22,59 @@ namespace :better_auth do
22
22
  namespace :generate do
23
23
  desc "Create the Better Auth SQL migration"
24
24
  task :migration do
25
- BetterAuth::Sinatra.load_app_config
25
+ BetterAuth::Sinatra.load_app_config!
26
26
  dialect = BetterAuth::Sinatra::Migration.normalize_dialect(BetterAuth::Env.get("BETTER_AUTH_DIALECT") || BetterAuth::Env.get("BETTER_AUTH_DATABASE_DIALECT") || "postgres")
27
27
  config = BetterAuth::Sinatra.migration_configuration
28
- path = BetterAuth::Sinatra::Migration.generate(config, dialect: dialect)
29
- puts "create #{path}"
28
+ adapter = begin
29
+ BetterAuth::Sinatra.auth.context.adapter
30
+ rescue
31
+ nil
32
+ end
33
+ connection = if adapter&.respond_to?(:connection) && adapter.respond_to?(:dialect) && BetterAuth::Sinatra::Migration.normalize_dialect(adapter.dialect) == dialect
34
+ adapter.connection
35
+ end
36
+ path = BetterAuth::Sinatra::Migration.generate(config, dialect: dialect, connection: connection)
37
+ puts(path ? "create #{path}" : "no migrations needed")
30
38
  end
31
39
  end
32
40
 
33
41
  desc "Run pending Better Auth SQL migrations"
34
42
  task :migrate do
35
- BetterAuth::Sinatra.load_app_config
43
+ BetterAuth::Sinatra.load_app_config!
36
44
  BetterAuth::Sinatra::Migration.migrate(BetterAuth::Sinatra.auth)
37
45
  end
38
46
 
47
+ namespace :migrate do
48
+ desc "Print pending Better Auth SQL migration status"
49
+ task :status do
50
+ BetterAuth::Sinatra.load_app_config!
51
+ auth = BetterAuth::Sinatra.auth
52
+ adapter = auth.context.adapter
53
+ unless adapter.respond_to?(:connection) && adapter.respond_to?(:dialect)
54
+ raise BetterAuth::Sinatra::Migration::UnsupportedAdapterError, "Better Auth SQL migrations require core SQL adapters with connection and dialect support"
55
+ end
56
+ plan = BetterAuth::Sinatra::Migration.plan(auth.options, connection: adapter.connection, dialect: adapter.dialect)
57
+ if plan.empty?
58
+ puts "No migrations needed."
59
+ else
60
+ plan.to_create.each { |change| puts "create table #{change.table_name}" }
61
+ plan.to_add.each { |change| puts "add #{change.fields.keys.join(", ")} to #{change.table_name}" }
62
+ plan.to_index.each { |change| puts "create index #{change.name}" }
63
+ plan.warnings.each { |warning| puts "warning: #{warning}" }
64
+ end
65
+ end
66
+ end
67
+
68
+ desc "Check Better Auth configuration and schema health"
69
+ task :doctor do
70
+ BetterAuth::Sinatra.load_app_config!
71
+ exit_code = BetterAuth::Doctor.print(BetterAuth::Doctor.check(BetterAuth::Sinatra.migration_configuration), stdout: $stdout, stderr: $stderr)
72
+ abort if exit_code != 0
73
+ end
74
+
39
75
  desc "Print Better Auth Sinatra mount information"
40
76
  task :routes do
41
- BetterAuth::Sinatra.load_app_config
77
+ BetterAuth::Sinatra.load_app_config!
42
78
  mount_path = BetterAuth::Sinatra.configuration.base_path
43
79
  puts "#{mount_path}/* -> BetterAuth.auth"
44
80
  puts "Core routes are handled by Better Auth; use the OpenAPI plugin or HTTP API docs for endpoint details."
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BetterAuth
4
4
  module Sinatra
5
- VERSION = "0.8.0"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
@@ -45,8 +45,25 @@ module BetterAuth
45
45
  BetterAuth::Configuration.new(options)
46
46
  end
47
47
 
48
- def load_app_config(path = "config/better_auth.rb")
49
- load path if File.exist?(path)
48
+ def app_config_path(path = nil)
49
+ path || BetterAuth::Env.get("BETTER_AUTH_CONFIG") || "config/better_auth.rb"
50
+ end
51
+
52
+ def load_app_config(path = nil)
53
+ config_path = app_config_path(path)
54
+ return false unless File.exist?(config_path)
55
+
56
+ load config_path
57
+ true
58
+ end
59
+
60
+ def load_app_config!(path = nil)
61
+ config_path = app_config_path(path)
62
+ return true if load_app_config(config_path)
63
+
64
+ raise ArgumentError,
65
+ "Better Auth Sinatra config not found at #{config_path.inspect}. " \
66
+ "Run `rake better_auth:install` or set BETTER_AUTH_CONFIG to a shared config file."
50
67
  end
51
68
 
52
69
  def default_config_template
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_auth-sinatra
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Sala
@@ -134,14 +134,14 @@ files:
134
134
  - lib/better_auth/sinatra/tasks.rb
135
135
  - lib/better_auth/sinatra/version.rb
136
136
  - lib/better_auth_sinatra.rb
137
- homepage: https://github.com/sebasxsala/better-auth
137
+ homepage: https://github.com/sebasxsala/better-auth-rb
138
138
  licenses:
139
139
  - MIT
140
140
  metadata:
141
- homepage_uri: https://github.com/sebasxsala/better-auth
142
- source_code_uri: https://github.com/sebasxsala/better-auth
143
- changelog_uri: https://github.com/sebasxsala/better-auth/blob/main/packages/better_auth-sinatra/CHANGELOG.md
144
- bug_tracker_uri: https://github.com/sebasxsala/better-auth/issues
141
+ homepage_uri: https://github.com/sebasxsala/better-auth-rb
142
+ source_code_uri: https://github.com/sebasxsala/better-auth-rb
143
+ changelog_uri: https://github.com/sebasxsala/better-auth-rb/blob/main/packages/better_auth-sinatra/CHANGELOG.md
144
+ bug_tracker_uri: https://github.com/sebasxsala/better-auth-rb/issues
145
145
  rdoc_options: []
146
146
  require_paths:
147
147
  - lib