studio-engine 0.5.5 → 0.5.7

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: 5ceb657a1e35df5801b4bf45a82d916d236d5497bd876b9b245997474411c310
4
- data.tar.gz: 6d68e9c3fb7a338b22adf45dd00435d21e16ddfce4321c5e28eabd9108a93ba1
3
+ metadata.gz: 6f49e5815738a1a37139e2a5f0667daa9d2a3db14f301b7e627e441100258c3b
4
+ data.tar.gz: b5b94d83793c7b408332d99fc60c198dc07e7d6bb6cd342de060bb41dd5a34fd
5
5
  SHA512:
6
- metadata.gz: d2ffbe056170f338dca804feeaf5594aa470d0f26488514ba798403108d6091e3072fa58b92a2fb8908c14d14d26cff7620fdbfcb8cd04a4f984316d90ec05b2
7
- data.tar.gz: 550d7dd3d351624c058d0a4db421be26c18dc0b357af54c3e9c575164bd78b908798e7e66fe87cd64b3dde648372337938ca4f7b31c52f8438676f58ba5c8d90
6
+ metadata.gz: 3fe90400d7a5e3653fc6fe7db6de0ffd3b6e3d03bb84b56c4a1cf200313178725068c30e58fa36c243e8166c6ce32910a14f2ef319b1a126855beb09ea835527
7
+ data.tar.gz: e253f58c29679abc6d70bd66c73c6d83012f2e7dacc118a9312b13395317115bda7558e7f7be55fcaff05e64395b8f082f8284eae90fca90c4476a576bf83e9a
data/CHANGELOG.md CHANGED
@@ -4,7 +4,31 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
- No entries yet.
7
+ ## v0.5.7 (2026-06-14)
8
+
9
+ ### Added
10
+ - **`Studio.mailer_from_for_transport` / `Studio.marketing_from_for_transport`** —
11
+ provider-aware sender helpers. SES-ready apps use app/domain-specific
12
+ `MAILER_FROM` and `MARKETING_MAILER_FROM`; Resend fallback uses
13
+ `RESEND_MAILER_FROM`, defaulting to `McRitchie Studio <team@mcritchie.studio>`
14
+ so new apps can send during SES sandbox/presetup without verifying a second
15
+ Resend domain.
16
+
17
+ ### Fixed
18
+ - **`ses:check` / `ses:verify_domain` credential selection** now prefers
19
+ `SES_AWS_ACCESS_KEY_ID` / `SES_AWS_SECRET_ACCESS_KEY` before falling back to
20
+ generic `AWS_*` credentials, keeping SES account checks separate from
21
+ consumer app S3/ImageCache IAM users.
22
+ - **`ses:verify_domain` existing identity handling** now accepts AWS SES's
23
+ `already exist` response wording and falls back to reading the existing
24
+ identity.
25
+
26
+ ## v0.5.6 (2026-06-14)
27
+
28
+ ### Added
29
+ - **`Studio.wallet_address_method` / `Studio.user_wallet_address(user)`** —
30
+ shared wallet-address adapter for SSO/session awareness. Defaults support
31
+ `wallet_address` and `solana_address`; apps can configure another method.
8
32
 
9
33
  ## v0.5.5 (2026-06-14)
10
34
 
data/README.md CHANGED
@@ -11,7 +11,7 @@ Shared Rails engine for McRitchie apps. Provides authentication, error handling,
11
11
  gem "studio-engine", "~> 0.5"
12
12
  ```
13
13
 
14
- Then `bundle install`. The current release is **v0.5.5**; see [`CHANGELOG.md`](./CHANGELOG.md) for the history.
14
+ Then `bundle install`. The current release is **v0.5.7**; see [`CHANGELOG.md`](./CHANGELOG.md) for the history.
15
15
 
16
16
  > Published to RubyGems as of v0.4.0 (2026-05-17). New installs should use the RubyGems form, which the consumer Rails apps (`mcritchie-studio`, `turf-monster`) already use.
17
17
 
@@ -35,7 +35,9 @@ Studio.configure do |config|
35
35
  config.auth_methods = %i[magic_link google]
36
36
  config.registration_params = [:name, :email]
37
37
  config.magic_link_token_name = "magic_link_my_app_v1"
38
- config.mailer_from = ENV.fetch("MAILER_FROM", "noreply@example.com")
38
+ config.mailer_from = Studio.mailer_from_for_transport(
39
+ ses_from: "My App <team@example.com>"
40
+ )
39
41
  config.theme_primary = "#4BAF50" # Override default violet
40
42
  config.theme_logos = ["logo.svg"]
41
43
  end
@@ -105,4 +107,7 @@ For short local experiments, temporarily point a consumer Gemfile at `path: "../
105
107
 
106
108
  ## Development Notes
107
109
 
108
- See [CLAUDE.md](./CLAUDE.md) only as legacy migration context while neutral agent docs are being extracted. Current cross-repo setup, ports, credentials, and workflow guidance live in McRitchie Studio's [`docs/agents/`](https://github.com/amcritchie/mcritchie-studio/tree/main/docs/agents).
110
+ Use the docs in [`docs/`](./docs) for engine setup, release, email transport,
111
+ and host-app contracts. Current cross-repo setup, ports, credentials, and
112
+ workflow guidance live in McRitchie Studio's
113
+ [`docs/agents/`](https://github.com/amcritchie/mcritchie-studio/tree/main/docs/agents).
@@ -49,7 +49,7 @@ module Studio
49
49
  session[:sso_name] = user.try(:name)
50
50
  session[:sso_provider] = user.provider
51
51
  session[:sso_uid] = user.uid
52
- session[:sso_wallet] = user.try(:wallet_address)
52
+ session[:sso_wallet] = Studio.user_wallet_address(user)
53
53
  session[:sso_source] = Studio.app_name
54
54
  session[:sso_logo] = Studio.sso_logo
55
55
  end
@@ -3,5 +3,5 @@ class ApplicationMailer < ActionMailer::Base
3
3
  # the host's config/initializers/studio.rb, with an ENV fallback. No layout is
4
4
  # forced — ActionMailer renders bare if the host ships no mailer layout, and a
5
5
  # host that defines its own ApplicationMailer (e.g. turf-monster) wins outright.
6
- default from: -> { Studio.mailer_from || ENV["MAILER_FROM"] || "no-reply@mcritchie.studio" }
6
+ default from: -> { Studio.mailer_from || ENV["MAILER_FROM"] || "McRitchie Studio <team@mcritchie.studio>" }
7
7
  end
@@ -63,6 +63,8 @@ class SessionContext
63
63
 
64
64
  # Primary wallet address (web3 preferred), or nil when logged out / wallet-less.
65
65
  def address
66
+ return Studio.user_wallet_address(user) if defined?(Studio) && Studio.respond_to?(:user_wallet_address)
67
+
66
68
  return nil unless user.respond_to?(:solana_address)
67
69
  user.solana_address
68
70
  end
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.5.5"
2
+ VERSION = "0.5.7"
3
3
  end
data/lib/studio.rb CHANGED
@@ -16,6 +16,7 @@ module Studio
16
16
  mattr_accessor :configure_new_user, default: ->(user) {}
17
17
  mattr_accessor :configure_sso_user, default: ->(user) {}
18
18
  mattr_accessor :sso_logo, default: nil
19
+ mattr_accessor :wallet_address_method, default: nil
19
20
  mattr_accessor :theme_logos, default: []
20
21
 
21
22
  # ---- Authentication ------------------------------------------------------
@@ -40,6 +41,7 @@ module Studio
40
41
  # Default From: for engine-sent mail (magic links). Apps set this to their
41
42
  # verified sending address in config/initializers/studio.rb.
42
43
  mattr_accessor :mailer_from, default: nil
44
+ mattr_accessor :resend_mailer_from, default: "McRitchie Studio <team@mcritchie.studio>"
43
45
 
44
46
  # Local/worktree email capture. nil means "auto": enabled when AGENT_WORKTREE
45
47
  # is truthy, otherwise disabled. Production always disables capture.
@@ -87,6 +89,36 @@ module Studio
87
89
  yield self
88
90
  end
89
91
 
92
+ def self.mailer_from_for_transport(env: ENV, ses_from:, resend_from: nil)
93
+ if ses_transport_ready?(env)
94
+ env_value(env, "MAILER_FROM") || ses_from
95
+ else
96
+ env_value(env, "RESEND_MAILER_FROM") || resend_from || resend_mailer_from
97
+ end
98
+ end
99
+
100
+ def self.marketing_from_for_transport(env: ENV, ses_from:, resend_from: nil)
101
+ if ses_transport_ready?(env)
102
+ env_value(env, "MARKETING_MAILER_FROM") || ses_from
103
+ else
104
+ env_value(env, "RESEND_MARKETING_FROM") ||
105
+ env_value(env, "RESEND_MAILER_FROM") ||
106
+ resend_from ||
107
+ resend_mailer_from
108
+ end
109
+ end
110
+
111
+ def self.ses_transport_ready?(env = ENV)
112
+ env["MAIL_TRANSPORT"].to_s.downcase == "ses" &&
113
+ env_value(env, "SES_SMTP_USERNAME") &&
114
+ env_value(env, "SES_SMTP_PASSWORD")
115
+ end
116
+
117
+ def self.env_value(env, key)
118
+ value = env[key]
119
+ value if value && !value.to_s.strip.empty?
120
+ end
121
+
90
122
  # True when the given sign-in method is enabled for this app.
91
123
  def self.auth_method?(method)
92
124
  auth_methods.include?(method.to_sym)
@@ -99,6 +131,19 @@ module Studio
99
131
  env_truthy?(ENV["LOCAL_EMAIL_CAPTURE"]) || env_truthy?(ENV["AGENT_WORKTREE"])
100
132
  end
101
133
 
134
+ def self.user_wallet_address(user)
135
+ return nil unless user
136
+
137
+ [wallet_address_method, :wallet_address, :solana_address].compact.each do |method|
138
+ next unless user.respond_to?(method)
139
+
140
+ value = user.public_send(method)
141
+ return value if value && !(value.respond_to?(:empty?) && value.empty?)
142
+ end
143
+
144
+ nil
145
+ end
146
+
102
147
  # Verifies that the host app's User model satisfies the engine's expected
103
148
  # contract. Raises Studio::UserContractError with a clear pointer to
104
149
  # docs/USER_CONTRACT.md if anything required is missing. Called from
@@ -2,14 +2,39 @@
2
2
 
3
3
  unless Rake::Task.task_defined?("ses:check")
4
4
  namespace :ses do
5
+ def studio_ses_env_value(name)
6
+ value = ENV[name]
7
+ return nil if value.nil? || value.to_s.strip.empty?
8
+
9
+ value
10
+ end
11
+
12
+ def studio_ses_credential(name)
13
+ studio_ses_env_value("SES_#{name}") || studio_ses_env_value(name)
14
+ end
15
+
16
+ def studio_ses_credential_source(name)
17
+ return "SES_#{name}" if studio_ses_env_value("SES_#{name}")
18
+ return name if studio_ses_env_value(name)
19
+
20
+ "missing"
21
+ end
22
+
5
23
  def studio_ses_signer(region)
6
24
  require "aws-sigv4"
7
25
 
26
+ access_key_id = studio_ses_credential("AWS_ACCESS_KEY_ID")
27
+ secret_access_key = studio_ses_credential("AWS_SECRET_ACCESS_KEY")
28
+ missing = []
29
+ missing << "SES_AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY_ID" unless access_key_id
30
+ missing << "SES_AWS_SECRET_ACCESS_KEY or AWS_SECRET_ACCESS_KEY" unless secret_access_key
31
+ abort "ses:* needs #{missing.join(' and ')}." if missing.any?
32
+
8
33
  Aws::Sigv4::Signer.new(
9
34
  service: "ses",
10
35
  region: region,
11
- access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID"),
12
- secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY")
36
+ access_key_id: access_key_id,
37
+ secret_access_key: secret_access_key
13
38
  )
14
39
  rescue LoadError
15
40
  abort "ses:* needs aws-sigv4; it is usually available through aws-sdk-s3."
@@ -45,6 +70,7 @@ unless Rake::Task.task_defined?("ses:check")
45
70
 
46
71
  code, account = get.call("/v2/email/account")
47
72
  puts "GetAccount (region #{region}) -> HTTP #{code}"
73
+ puts " CredentialSource=#{studio_ses_credential_source('AWS_ACCESS_KEY_ID')}"
48
74
  if code == 200
49
75
  puts " SendingEnabled=#{account['SendingEnabled']} ProductionAccessEnabled=#{account['ProductionAccessEnabled']} Enforcement=#{account['EnforcementStatus']}"
50
76
  else
@@ -74,8 +100,9 @@ unless Rake::Task.task_defined?("ses:check")
74
100
  signer = studio_ses_signer(region)
75
101
  body = { EmailIdentity: domain }.to_json
76
102
  code, response = studio_ses_request(signer, region, "POST", "/v2/email/identities", body)
103
+ already_exists = "#{response['message']} #{response['Message']}".match?(/already exist/i)
77
104
 
78
- if code == 409 || "#{response['message']}#{response['Message']}".match?(/already exists/i)
105
+ if code == 409 || ([400, 409].include?(code) && already_exists)
79
106
  code, response = studio_ses_request(signer, region, "GET", "/v2/email/identities/#{domain}")
80
107
  end
81
108
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-06-14 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -227,7 +226,6 @@ metadata:
227
226
  source_code_uri: https://github.com/amcritchie/studio-engine/tree/main
228
227
  bug_tracker_uri: https://github.com/amcritchie/studio-engine/issues
229
228
  changelog_uri: https://github.com/amcritchie/studio-engine/blob/main/CHANGELOG.md
230
- post_install_message:
231
229
  rdoc_options: []
232
230
  require_paths:
233
231
  - lib
@@ -242,8 +240,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
240
  - !ruby/object:Gem::Version
243
241
  version: '0'
244
242
  requirements: []
245
- rubygems_version: 3.5.11
246
- signing_key:
243
+ rubygems_version: 4.0.9
247
244
  specification_version: 4
248
245
  summary: Shared Rails engine providing auth, SSO, error logging, theming, and S3-backed
249
246
  image caching