studio-engine 0.74.0 → 0.74.1
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/CHANGELOG.md +108 -1
- data/app/views/components/_sidebar_panel.html.erb +13 -3
- data/app/views/studio/board/_card_shell.html.erb +14 -0
- data/app/views/studio/modals/blocks/_leveling_activity.html.erb +23 -5
- data/app/views/studio/modals/blocks/_processing_card.html.erb +20 -1
- data/app/views/studio/modals/blocks/_success_card.html.erb +19 -2
- data/lib/studio/js_literal.rb +13 -0
- data/lib/studio/mail_transport.rb +2 -2
- data/lib/studio/s3.rb +12 -4
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d056d9eddc4bd13c863b5ec0f3e239e7591fc16ddd2170ac5192537f5a190326
|
|
4
|
+
data.tar.gz: 289b7050571718689aa567db5f0434be21c41ee7074b4b49d26969f16b07a815
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15c2e97be72e8fe2e4e67719a569f6d456f82e961fd36b11d0c5062d8ace97836316398eef5058bf9436cdec7ec4ed33aca31ca7decf70bcfe3569dd8665e693
|
|
7
|
+
data.tar.gz: 6dd53cf3747fdb36271afaea5a76c0dc2ee615e25f394083b3219476a5222991999edf97474e1022e9f2f584350ba05b04cbfaa01d5fe0f28ce2995f3a06d6fa
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,111 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
6
6
|
|
|
7
7
|
### Fixed
|
|
8
8
|
|
|
9
|
+
- **A quote in a host local can no longer close an Alpine attribute and turn the
|
|
10
|
+
rest of the element into markup.** Four partials built an attribute — its OWN
|
|
11
|
+
QUOTES included — as a Ruby String and marked it `html_safe`, which is the one
|
|
12
|
+
shape ERB's attribute escaping can never reach: the marking tells ERB to stand
|
|
13
|
+
down, so a double quote in the local ended the attribute and the HTML parser read
|
|
14
|
+
the remainder as new attributes. Measured, not reasoned: one hostile
|
|
15
|
+
`auto_redirect_seconds` turned the success card's root into an element carrying
|
|
16
|
+
attributes named `quote"`, `\`, `<` and `script`, with its `class` swallowed
|
|
17
|
+
whole. This is strictly worse than the JS-string-literal class fixed in
|
|
18
|
+
*Host-supplied locals can no longer brick an Alpine component* below, which at
|
|
19
|
+
least stayed inside its attribute.
|
|
20
|
+
|
|
21
|
+
**THE REPAIR IS NOT ESCAPING.** These locals are Alpine EXPRESSIONS by contract —
|
|
22
|
+
the caller is handing the engine JS on purpose — so `escape_javascript` would
|
|
23
|
+
break the feature it was protecting. The partials stop writing the quotes and let
|
|
24
|
+
ActionView write them: `tag.attributes("x-init": expr.html_safe)`. `tag_option`
|
|
25
|
+
runs `gsub('"', """)` on the finished value UNCONDITIONALLY, on the line
|
|
26
|
+
AFTER the escape branch it skips for a marked String, so the expression keeps its
|
|
27
|
+
apostrophes, angle brackets and backslashes byte-for-byte and still cannot end the
|
|
28
|
+
attribute. `blocks/_rail_row:112` already did exactly this and was the worked
|
|
29
|
+
example.
|
|
30
|
+
|
|
31
|
+
**WHAT MOVED — twelve splices in four files.** `blocks/_success_card` (the
|
|
32
|
+
Ruby-built `x-data` countdown object, and the `x-init` call list that splices
|
|
33
|
+
`auto_redirect_url_key`), `blocks/_processing_card` (one assembled `x-data`/`x-init`
|
|
34
|
+
pair carrying TWO locals — `resolve_expr` and `min_duration`, which only LOOKS
|
|
35
|
+
numeric because just its guard calls `.to_i`), `blocks/_leveling_activity` (the
|
|
36
|
+
Next Quest `@click`, plus `minlength` / `maxlength` / `pattern` / `title` on the
|
|
37
|
+
form input), and `components/_sidebar_panel` (`@click.outside`,
|
|
38
|
+
`@keydown.escape.window` and `@turbo:before-cache.window` on the root of the shell
|
|
39
|
+
every link menu hangs from). No default or in-repo value contains a character tag
|
|
40
|
+
encoding touches, so every shipped page renders byte-for-byte what it did — a
|
|
41
|
+
render-level test asserts that directly.
|
|
42
|
+
|
|
43
|
+
**THE MARKING NOW FOLLOWS WHAT THE VALUE IS.** Code stays `html_safe` and reaches
|
|
44
|
+
the browser unchanged; CONTENT does not. The four input constraint attributes are
|
|
45
|
+
a length, a regex and a tooltip, so they take the full escaping a content
|
|
46
|
+
attribute is owed — which also closes a second latent bug the marking carried, a
|
|
47
|
+
bare `&` in a `title` or `pattern` reaching the browser as the start of an entity.
|
|
48
|
+
|
|
49
|
+
**TWO OF THE SIX SITES ON THE ORIGINAL LIST WERE ALREADY SAFE and were left
|
|
50
|
+
alone.** `blocks/_rail_row:112` routes its handler through `content_tag`, and
|
|
51
|
+
`board/_card_shell:54` escapes both halves of its arbitrary-attribute passthrough
|
|
52
|
+
through ERB. Both were re-verified by rendering them with a hostile value, not by
|
|
53
|
+
reading them. Filing a fix for either would have bought a guard that can never
|
|
54
|
+
bite.
|
|
55
|
+
|
|
56
|
+
**HOW THE GUARDS ASSERT.** `test/views/assembled_attribute_locals_test.rb` reads
|
|
57
|
+
every seam through Nokogiri's HTML5 parser — the algorithm a browser runs — and
|
|
58
|
+
asserts on the DECODED attribute value, never on the template's output bytes,
|
|
59
|
+
because ERB entity-escapes an unmarked value too and a raw-markup structural
|
|
60
|
+
assertion therefore cannot fail. (HTML5 rather than HTML4 on purpose: HTML4
|
|
61
|
+
silently drops an `@click`, so every Alpine assertion made through it reads nil on
|
|
62
|
+
a correct page.) Each seam makes two claims — the element carries exactly the
|
|
63
|
+
attributes a benign value gives it, and the browser recovers the whole expression
|
|
64
|
+
— plus a non-vacuity control that fails if the partial ever stops splicing the
|
|
65
|
+
local at all. All twelve were mutated back to their assembled form ONE AT A TIME
|
|
66
|
+
and each was killed by its own test; the shared `_processing_card` call was also
|
|
67
|
+
mutated per-local. `test/lib/studio/attribute_encoding_contract_test.rb` pins the
|
|
68
|
+
ActionView property the whole repair stands on, so a future Rails moving that
|
|
69
|
+
`gsub` reports itself as one dependency change rather than twelve partial bugs.
|
|
70
|
+
|
|
71
|
+
**STILL OPEN, and named rather than quietly folded in:**
|
|
72
|
+
`studio/mailers/_layered_banner:69,100` assemble `background=` and `bgcolor=` the
|
|
73
|
+
same way. Same shape, different family — email attributes, no Alpine, no JS — so
|
|
74
|
+
they are left for their own ticket rather than widened into this one.
|
|
75
|
+
|
|
76
|
+
- **The Rails guard sweep really is finished now, and a test says so instead of a
|
|
77
|
+
comment.** Fixing `Studio::S3` left THREE sites still guarding a Rails method
|
|
78
|
+
call on a bare `defined?(Rails)`: both keyword defaults in
|
|
79
|
+
`Studio::MailTransport.configure!` (`rails_env:` reading `Rails.env`, `logger:`
|
|
80
|
+
reading `Rails.logger`) and the developer-desk route guard in
|
|
81
|
+
`Studio.routes`. All three now ask `Rails.respond_to?` first.
|
|
82
|
+
|
|
83
|
+
**THE MAIL TRANSPORT ONE WAS ARMED, not theoretical.** `lib/studio.rb` requires
|
|
84
|
+
`studio/js_literal` (line 11) — whose first line is `require "action_view"`,
|
|
85
|
+
which is what defines the namespace-only `module Rails` — BEFORE it requires
|
|
86
|
+
`studio/mail_transport` (line 25). Measured at this branch's head, a bare
|
|
87
|
+
`Studio::MailTransport.configure!(env: {}, action_mailer: mailer)` died with
|
|
88
|
+
`NoMethodError: undefined method 'env' for module Rails`. No shipped app can
|
|
89
|
+
reach it (every host configures mail from an initializer, where `Rails.env`
|
|
90
|
+
exists, and all four in-repo callers pass `rails_env:` explicitly) — the cost
|
|
91
|
+
lands on the next gem unit test that omits the kwarg, which is the same
|
|
92
|
+
half-hour the `Studio::S3` fix already paid once.
|
|
93
|
+
|
|
94
|
+
**THE TWO DEFAULTS ARE SEPARATE STRAGGLERS**, and the first hid the second: Ruby
|
|
95
|
+
evaluates only the defaults a caller omitted, and `rails_env:` is declared above
|
|
96
|
+
`logger:`, so a bare call raised out of `Rails.env` and never reached
|
|
97
|
+
`Rails.logger`. `test/lib/studio/mail_transport_namespace_only_rails_test.rb`
|
|
98
|
+
supplies one argument and omits the other in each test, so each guard is pinned
|
|
99
|
+
independently rather than behind its neighbour.
|
|
100
|
+
|
|
101
|
+
**THE ROUTE GUARD IS PINNED BY A SCAN, deliberately.** `Studio.routes` only
|
|
102
|
+
loads inside a real Rails application, where `Rails.env` exists — so the
|
|
103
|
+
condition under test cannot be constructed where the file loads, and no
|
|
104
|
+
behavioural test can reach it. `test/lib/studio/rails_guard_sweep_test.rb` is
|
|
105
|
+
the tree-wide net instead, and it asserts both that it really read the files and
|
|
106
|
+
that its predicate still flags the original shape, so it cannot pass for free.
|
|
107
|
+
|
|
108
|
+
**The completeness claim itself was the other half of the bug.** `lib/studio/s3.rb`
|
|
109
|
+
and this changelog both said that fix "was the straggler" — while one of the
|
|
110
|
+
remaining three sat in `lib/studio.rb`, the very file the comment cited as
|
|
111
|
+
already clean. Both claims are corrected, and the comment now points at the test
|
|
112
|
+
rather than restating that the sweep is done.
|
|
113
|
+
|
|
9
114
|
- **The app census in these comments was short by the most important app.** Ten
|
|
10
115
|
sites said this engine is mounted by SIX apps and that THREE of them bundle no
|
|
11
116
|
`solana-studio`. Measured 2026-09-07 by the criterion that reproduces it — a
|
|
@@ -363,7 +468,9 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
363
468
|
singleton methods, so that guard reads true and the next call raises
|
|
364
469
|
`NoMethodError: undefined method 'env' for module Rails`. It now asks
|
|
365
470
|
`Rails.respond_to?(:env)`, which is the form `lib/studio.rb` already uses in three
|
|
366
|
-
places
|
|
471
|
+
places. **This entry originally called it "the straggler"; that was wrong** —
|
|
472
|
+
three more sites carried the bare form, swept in *The Rails guard sweep really is
|
|
473
|
+
finished now* above. No shipped app can reach it — every host boots a
|
|
367
474
|
real application — but the engine's own pure-Ruby unit lane can, and it did:
|
|
368
475
|
adding one `require` for a file that needs `action_view` turned an untouched
|
|
369
476
|
`email_catalog_test` red with two errors about email uploads.
|
|
@@ -34,9 +34,19 @@
|
|
|
34
34
|
<% if id.present? %>id="<%= id %>"<% end %>
|
|
35
35
|
x-cloak
|
|
36
36
|
x-show="<%= open %>"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
<%# THE THREE DISMISSAL HANDLERS, EMITTED BY tag.attributes. Each was built as
|
|
38
|
+
%(@name="#{action}").html_safe — the attribute's OWN quotes assembled in Ruby
|
|
39
|
+
and then marked safe, which is the one shape ERB's escaping can never reach. A
|
|
40
|
+
double quote in any of the three actions closed the attribute and everything
|
|
41
|
+
after it became markup on the panel's root element.
|
|
42
|
+
|
|
43
|
+
The actions stay html_safe: they are Alpine EXPRESSIONS the host wrote (see the
|
|
44
|
+
@click at the close button below, and `open` above). tag_option's
|
|
45
|
+
gsub('"', """) runs on the finished value whether or not it is marked, so
|
|
46
|
+
the expression reaches Alpine unchanged and cannot end the attribute. %>
|
|
47
|
+
<%= tag.attributes("@click.outside": outside_action.html_safe) if outside_action.present? %>
|
|
48
|
+
<%= tag.attributes("@keydown.escape.window": escape_action.html_safe) if escape_action.present? %>
|
|
49
|
+
<%= tag.attributes("@turbo:before-cache.window": close_action.html_safe) if close_action.present? %>
|
|
40
50
|
x-transition:enter="transition ease-out duration-300"
|
|
41
51
|
x-transition:enter-start="translate-x-full"
|
|
42
52
|
x-transition:enter-end="translate-x-0"
|
|
@@ -32,6 +32,20 @@
|
|
|
32
32
|
attrs: Hash of extra root attributes ({ "x-data" => "…", "data-foo" => 1 }),
|
|
33
33
|
values are HTML-escaped — the rebase seam for a card that carries
|
|
34
34
|
its own Alpine/data-* on the root.
|
|
35
|
+
|
|
36
|
+
NOT A CANDIDATE FOR THE ASSEMBLED-ATTRIBUTE SWEEP: nothing here is
|
|
37
|
+
marked html_safe, so ERB escapes both halves. A double quote in a
|
|
38
|
+
VALUE becomes " and the parser decodes it back — the value half
|
|
39
|
+
cannot break out. Verified 2026-09-08 by rendering, not reading.
|
|
40
|
+
|
|
41
|
+
THE NAME HALF IS A DIFFERENT QUESTION, and entity decoding is not
|
|
42
|
+
what answers it. html_escape does not touch a SPACE, and a space
|
|
43
|
+
ENDS an attribute name: a name of `data-y=1 x-init` renders as
|
|
44
|
+
`data-y=1 x-init="…"` and the parser builds a real SECOND attribute
|
|
45
|
+
— measured, an injected x-init Alpine will run. What makes this seam
|
|
46
|
+
safe today is that nothing passes attrs: at all — zero call sites in
|
|
47
|
+
studio-engine, turf-monster and mcritchie-studio. Its first caller
|
|
48
|
+
owes an allow-list on the NAME before this emits it.
|
|
35
49
|
%>
|
|
36
50
|
<%
|
|
37
51
|
id = local_assigns.fetch(:id)
|
|
@@ -138,10 +138,23 @@
|
|
|
138
138
|
<% if input %>
|
|
139
139
|
<input type="<%= input_type %>" x-model="value" class="input-field w-full"
|
|
140
140
|
placeholder="<%= placeholder %>"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
<%# FOUR CONSTRAINT ATTRIBUTES, ENCODED AND — unlike the Alpine
|
|
142
|
+
handlers in this file — DELIBERATELY NOT html_safe. Each was
|
|
143
|
+
assembled as `attr="#{value}"`.html_safe, which handed ERB a
|
|
144
|
+
String it must not touch, so a double quote in any of the four
|
|
145
|
+
closed the attribute and the rest became markup. minlength
|
|
146
|
+
looks numeric and is not: only the GUARD calls .to_i, the
|
|
147
|
+
interpolation splices the local raw.
|
|
148
|
+
|
|
149
|
+
These are CONTENT, not code — a length, a regex, a tooltip
|
|
150
|
+
sentence — so they take the full escaping a content attribute
|
|
151
|
+
is owed and the parser decodes it back. That also repairs a
|
|
152
|
+
second latent bug the marking carried: a bare & in a title or
|
|
153
|
+
pattern used to reach the browser as the start of an entity. %>
|
|
154
|
+
<%= tag.attributes(minlength: min_length) if min_length.to_i.positive? %>
|
|
155
|
+
<%= tag.attributes(maxlength: max_length) if max_length.present? %>
|
|
156
|
+
<%= tag.attributes(pattern: pattern) if pattern.present? %>
|
|
157
|
+
<%= tag.attributes(title: pattern_title) if pattern_title.present? %>
|
|
145
158
|
autofocus>
|
|
146
159
|
<% end %>
|
|
147
160
|
<% if consent_label.present? %>
|
|
@@ -180,7 +193,12 @@
|
|
|
180
193
|
</div>
|
|
181
194
|
|
|
182
195
|
<% if next_label.present? && next_open.present? %>
|
|
183
|
-
|
|
196
|
+
<%# next_open is html_safe (:102) because it is a developer-authored Alpine
|
|
197
|
+
EXPRESSION — so ERB steps aside here and a double quote in it used to
|
|
198
|
+
close the @click attribute outright. tag.attributes writes the quotes and
|
|
199
|
+
entity-encodes any of its own that appear in the value, leaving the
|
|
200
|
+
expression otherwise byte-identical. %>
|
|
201
|
+
<button type="button" <%= tag.attributes("@click": next_open) %> class="btn btn-primary btn-lg w-full">
|
|
184
202
|
<%= next_label %>
|
|
185
203
|
</button>
|
|
186
204
|
<% end %>
|
|
@@ -41,8 +41,27 @@
|
|
|
41
41
|
# JS sides of the convention agree on the same floor.
|
|
42
42
|
min_duration = local_assigns[:min_duration] || 1400
|
|
43
43
|
resolve_expr = local_assigns[:resolve_expr]
|
|
44
|
+
|
|
45
|
+
# THE AUTO-RESOLVE PAIR, ENCODED RATHER THAN ASSEMBLED. This line used to build
|
|
46
|
+
# ` x-data="{}" x-init="…"` as one html_safe String, quotes and all, so ERB had
|
|
47
|
+
# nothing left to escape: a double quote in EITHER local — min_duration as much as
|
|
48
|
+
# resolve_expr, since min_duration is interpolated raw and only its GUARD calls
|
|
49
|
+
# .to_i — closed the attribute and turned the remainder into markup.
|
|
50
|
+
#
|
|
51
|
+
# tag.attributes writes the quotes instead. The x-init value stays html_safe
|
|
52
|
+
# because it is CODE the caller handed us, and tag_option's unconditional
|
|
53
|
+
# gsub('"', """) still fires on an html_safe value, so the JS arrives intact
|
|
54
|
+
# and the attribute cannot end early. safe_join supplies the separating space
|
|
55
|
+
# without a second hand-marked String.
|
|
56
|
+
resolve_attrs =
|
|
57
|
+
if resolve_expr
|
|
58
|
+
safe_join([" ", tag.attributes(
|
|
59
|
+
"x-data": "{}",
|
|
60
|
+
"x-init": "window.StudioModals.holdAtLeast(#{min_duration}).then(() => { #{resolve_expr} })".html_safe
|
|
61
|
+
)])
|
|
62
|
+
end
|
|
44
63
|
%>
|
|
45
|
-
<div class="text-center py-6"<%=
|
|
64
|
+
<div class="text-center py-6"<%= resolve_attrs %>>
|
|
46
65
|
<div class="mx-auto <%= spinner_class %> rounded-full border-<%= color %>/30 border-t-<%= color %> animate-spin mb-5"></div>
|
|
47
66
|
<% if local_assigns[:title_key] %>
|
|
48
67
|
<p class="text-base font-bold text-heading mb-1" x-text="<%= title_key %>"></p>
|
|
@@ -122,9 +122,26 @@
|
|
|
122
122
|
init_calls = []
|
|
123
123
|
init_calls << "fireConfetti()" if fire_confetti
|
|
124
124
|
init_calls << "startCountdown(#{local_assigns[:auto_redirect_url_key]})" if has_redirect
|
|
125
|
+
init_expr = init_calls.join("; ").html_safe
|
|
125
126
|
%>
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
<%# BOTH ALPINE ATTRIBUTES ARE EMITTED BY tag.attributes, NOT ASSEMBLED BY HAND.
|
|
128
|
+
Each carries Ruby-built JS that splices a host local — redirect_secs into the
|
|
129
|
+
x-data object, auto_redirect_url_key into startCountdown() — and both blobs are
|
|
130
|
+
html_safe, because they are CODE and the caller is owed the JS it wrote.
|
|
131
|
+
|
|
132
|
+
THAT MARKING USED TO BE THE HOLE. html_safe tells ERB to step aside, so a double
|
|
133
|
+
quote in either local closed the ATTRIBUTE and everything after it became markup:
|
|
134
|
+
x-data="{ _remaining: it's a " quote" … — measured, not reasoned.
|
|
135
|
+
|
|
136
|
+
tag.attributes closes it without touching the JS. ActionView's tag_option runs
|
|
137
|
+
gsub('"', """) on the finished value UNCONDITIONALLY, outside the escape
|
|
138
|
+
branch it skips for an html_safe string (actionview tag_helper.rb, the line after
|
|
139
|
+
the escape ternary), so the attribute cannot be closed early and the HTML parser
|
|
140
|
+
decodes the entity back before Alpine ever sees it. Every other character reaches
|
|
141
|
+
the attribute byte-for-byte as it does today. blocks/_rail_row:112 is the same
|
|
142
|
+
move on a whole tag and its comment carries the rest of the reasoning. %>
|
|
143
|
+
<div <%= tag.attributes("x-data": data_attr) %>
|
|
144
|
+
<%= tag.attributes("x-init": init_expr) if init_calls.any? %>
|
|
128
145
|
class="text-center py-6">
|
|
129
146
|
|
|
130
147
|
<%# Icon — emoji takes priority over the default circular green check %>
|
data/lib/studio/js_literal.rb
CHANGED
|
@@ -43,6 +43,19 @@ module Studio
|
|
|
43
43
|
# safe). The name says `in_attribute` so that the next person reaching for this in
|
|
44
44
|
# a <script> has to stop and notice the difference.
|
|
45
45
|
#
|
|
46
|
+
# AND IT IS THE WRONG REPAIR FOR AN ATTRIBUTE THE PARTIAL ASSEMBLES ITSELF —
|
|
47
|
+
# `<%= "x-init=\"#{expr}\"".html_safe %>`, where the attribute's OWN QUOTES are
|
|
48
|
+
# built in Ruby. Nothing here helps: escaping the value would break the Alpine
|
|
49
|
+
# EXPRESSION the caller deliberately handed the engine, and the marking has already
|
|
50
|
+
# told ERB to stand down, so a double quote closes the ATTRIBUTE and the remainder
|
|
51
|
+
# is parsed as MARKUP. The repair is to stop writing the quotes and let ActionView
|
|
52
|
+
# write them — `tag.attributes("x-init": expr.html_safe)` — because tag_option
|
|
53
|
+
# quote-escapes the finished value UNCONDITIONALLY, outside the escape branch it
|
|
54
|
+
# skips for a marked String. The expression survives byte-for-byte and cannot end
|
|
55
|
+
# the attribute. modals/blocks/_rail_row:112 and components/_sidebar_panel carry
|
|
56
|
+
# the worked examples; test/lib/studio/attribute_encoding_contract_test.rb pins the
|
|
57
|
+
# ActionView behaviour the whole thing rests on.
|
|
58
|
+
#
|
|
46
59
|
# AND IT IS THE WRONG REPAIR FOR IDENTIFIER POSITION. A local spliced in as a bare
|
|
47
60
|
# NAME — `$store.<%= modal_store %>.close()` — must be VALIDATED, never escaped:
|
|
48
61
|
# escape_javascript also escapes `$`, so a legitimate store name like `dsModals$2`
|
|
@@ -6,9 +6,9 @@ module Studio
|
|
|
6
6
|
|
|
7
7
|
class << self
|
|
8
8
|
def configure!(env: ENV,
|
|
9
|
-
rails_env: defined?(Rails) ? Rails.env : "development",
|
|
9
|
+
rails_env: defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : "development",
|
|
10
10
|
action_mailer: defined?(ActionMailer) ? ActionMailer::Base : nil,
|
|
11
|
-
logger: defined?(Rails) ? Rails.logger : nil,
|
|
11
|
+
logger: defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : nil,
|
|
12
12
|
mailer_from: defined?(Studio) && Studio.respond_to?(:mailer_from) ? Studio.mailer_from : nil,
|
|
13
13
|
resend_loader: method(:load_resend!),
|
|
14
14
|
resend_configurer: method(:configure_resend!))
|
data/lib/studio/s3.rb
CHANGED
|
@@ -128,10 +128,18 @@ module Studio
|
|
|
128
128
|
# before any Rails APPLICATION does — defines a namespace-only `module
|
|
129
129
|
# Rails` with no singleton methods on it. Against that, a bare
|
|
130
130
|
# `defined?(Rails)` reads TRUE and the very next call dies with
|
|
131
|
-
# NoMethodError: undefined method `env` for module Rails.
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
131
|
+
# NoMethodError: undefined method `env` for module Rails. It turned an
|
|
132
|
+
# unrelated green suite red the first time a unit test pulled action_view
|
|
133
|
+
# in.
|
|
134
|
+
#
|
|
135
|
+
# THIS WAS NOT THE LAST ONE, though it was described that way when it
|
|
136
|
+
# landed. Three sites still carried the bare form afterwards — both
|
|
137
|
+
# keyword defaults in lib/studio/mail_transport.rb and the developer-desk
|
|
138
|
+
# route guard in lib/studio.rb — and the claim that the sweep was finished
|
|
139
|
+
# is how they survived a second review. The whole tree is swept now, and
|
|
140
|
+
# test/lib/studio/rails_guard_sweep_test.rb is what keeps it that way; do
|
|
141
|
+
# not restate completeness here, because a comment cannot notice the next
|
|
142
|
+
# straggler and that test can.
|
|
135
143
|
defined?(Rails) && Rails.respond_to?(:env) && Rails.env&.production? ? "production" : "dev"
|
|
136
144
|
end
|
|
137
145
|
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
|
@@ -856,7 +856,7 @@ module Studio
|
|
|
856
856
|
# Developer-desk tools. Drawn outside production, and each controller
|
|
857
857
|
# re-checks Studio.local_tool_enabled? per request (loopback only) — the
|
|
858
858
|
# route being absent is the outer gate, not the only one.
|
|
859
|
-
unless defined?(Rails) && Rails.env.production?
|
|
859
|
+
unless defined?(Rails) && Rails.respond_to?(:env) && Rails.env.production?
|
|
860
860
|
get "_studio/local_emails", to: "studio/local_emails#index", as: :studio_local_emails
|
|
861
861
|
get "_studio/local_review", to: "studio/local_reviews#show", as: :studio_local_review
|
|
862
862
|
end
|