astroidjs 0.12.1 → 0.13.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.
- package/README.md +42 -42
- package/bin/astroid.mjs +25 -25
- package/dist/analytics/index.d.ts +3 -3
- package/dist/analytics/index.js +9 -9
- package/dist/astro/csp.d.ts +5 -5
- package/dist/astro/csp.js +6 -6
- package/dist/astro/index.js +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.js +5 -5
- package/dist/commerce/adapters.d.ts +6 -6
- package/dist/commerce/adapters.js +9 -9
- package/dist/commerce/checkout-scaffold.d.ts +5 -5
- package/dist/commerce/checkout-scaffold.js +9 -9
- package/dist/commerce/checkout.d.ts +12 -12
- package/dist/commerce/checkout.js +7 -7
- package/dist/commerce/loader.d.ts +2 -2
- package/dist/commerce/loader.js +3 -3
- package/dist/commerce/mirror.d.ts +4 -4
- package/dist/commerce/mirror.js +9 -9
- package/dist/commerce/roles.d.ts +10 -10
- package/dist/commerce/roles.js +13 -13
- package/dist/commerce/secrets.d.ts +9 -9
- package/dist/commerce/secrets.js +9 -9
- package/dist/commerce/sync.d.ts +7 -7
- package/dist/commerce/sync.js +5 -5
- package/dist/components/sections.d.ts +9 -9
- package/dist/components/sections.js +12 -12
- package/dist/config.d.ts +62 -62
- package/dist/config.js +18 -18
- package/dist/email/inquiry.d.ts +2 -2
- package/dist/email/inquiry.js +1 -1
- package/dist/email/send.d.ts +4 -4
- package/dist/email/send.js +7 -7
- package/dist/email/templates.js +3 -3
- package/dist/email/theme.d.ts +1 -1
- package/dist/email/theme.js +4 -4
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.js +1 -1
- package/dist/map/pmtiles.d.ts +5 -5
- package/dist/map/pmtiles.js +5 -5
- package/dist/map/scaffold.d.ts +2 -2
- package/dist/map/scaffold.js +4 -4
- package/dist/map/style.d.ts +4 -4
- package/dist/map/style.js +1 -1
- package/dist/portal/config.d.ts +2 -2
- package/dist/portal/config.js +3 -3
- package/dist/portal/guard.d.ts +4 -4
- package/dist/portal/guard.js +4 -4
- package/dist/portal/nav.js +2 -2
- package/dist/portal/scaffold.d.ts +4 -4
- package/dist/portal/scaffold.js +6 -6
- package/dist/portal/session.d.ts +2 -2
- package/dist/portal/session.js +5 -5
- package/dist/portfolio/scaffold.d.ts +1 -1
- package/dist/portfolio/scaffold.js +4 -4
- package/dist/project/actions.d.ts +1 -1
- package/dist/project/actions.js +6 -6
- package/dist/project/generate.d.ts +4 -4
- package/dist/project/generate.js +15 -15
- package/dist/project/index.js +1 -1
- package/dist/project/scaffold.d.ts +2 -2
- package/dist/project/scaffold.js +11 -11
- package/dist/pwa/generate.d.ts +11 -11
- package/dist/pwa/generate.js +12 -12
- package/dist/queues/consumer.d.ts +3 -3
- package/dist/queues/consumer.js +2 -2
- package/dist/queues/messages.d.ts +4 -4
- package/dist/queues/messages.js +2 -2
- package/dist/queues/scaffold.d.ts +4 -4
- package/dist/queues/scaffold.js +8 -8
- package/dist/queues/webhook.d.ts +5 -5
- package/dist/queues/webhook.js +3 -3
- package/dist/realtime/scaffold.d.ts +4 -4
- package/dist/realtime/scaffold.js +8 -8
- package/dist/schema/collections.d.ts +6 -6
- package/dist/schema/collections.js +23 -23
- package/dist/schema/framework.d.ts +1 -1
- package/dist/schema/framework.js +2 -2
- package/dist/schema/generate.js +2 -2
- package/dist/schema/index.js +1 -1
- package/dist/secrets.d.ts +6 -6
- package/dist/secrets.js +6 -6
- package/dist/security/csp-origins.d.ts +1 -1
- package/dist/security/csp-origins.js +3 -3
- package/dist/security/rate-rules.d.ts +2 -2
- package/dist/security/rate-rules.js +8 -8
- package/dist/seo/resolve.d.ts +5 -5
- package/dist/seo/resolve.js +2 -2
- package/dist/seo/routes.d.ts +5 -5
- package/dist/seo/routes.js +3 -3
- package/dist/seo/structured-data.d.ts +6 -6
- package/dist/seo/structured-data.js +7 -7
- package/dist/status.d.ts +5 -5
- package/dist/status.js +7 -7
- package/dist/tenancy/index.d.ts +3 -3
- package/dist/tenancy/index.js +6 -6
- package/dist/worker/generate.d.ts +2 -2
- package/dist/worker/generate.js +19 -19
- package/dist/worker/index.js +1 -1
- package/dist/worker/routes.js +1 -1
- package/dist/workflow/advance.d.ts +3 -3
- package/dist/workflow/advance.js +6 -6
- package/dist/workflow/config.d.ts +4 -4
- package/dist/workflow/config.js +4 -4
- package/dist/workflow/generate.d.ts +2 -2
- package/dist/workflow/generate.js +4 -4
- package/package.json +3 -4
- package/src/components/Collection.tsx +5 -5
- package/src/components/Editable.astro +9 -9
- package/src/components/JustifiedGallery.astro +8 -8
- package/src/components/MediaSlot.astro +12 -12
- package/src/components/PortalShell.astro +4 -4
- package/src/components/RegisterSW.astro +3 -3
- package/src/components/Section.astro +8 -8
- package/src/components/Sections.astro +6 -6
- package/src/components/Seo.astro +3 -3
- package/src/components/StageBar.astro +3 -3
- package/src/components/StructuredData.astro +2 -2
- package/src/components/justify.ts +9 -9
- package/src/components/media-meta.ts +10 -10
- package/src/components/sections/AboutIntro.astro +1 -1
- package/src/components/sections/Contact.astro +1 -1
- package/src/components/sections/Cta.astro +1 -1
- package/src/components/sections/Faq.astro +1 -1
- package/src/components/sections/FeatureGrid.astro +2 -2
- package/src/components/sections/Hero.astro +1 -1
- package/src/components/sections/PricingTiers.astro +1 -1
- package/src/components/sections/ProductGrid.astro +1 -1
- package/src/components/sections/SplitImage.astro +1 -1
- package/src/components/sections/Steps.astro +1 -1
- package/src/components/sections/Testimonial.astro +1 -1
- package/src/components/sections.ts +17 -17
package/dist/config.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { astroidSectionCatalog } from "./components/sections.js";
|
|
|
5
5
|
import type { PortalRoute } from "./portal/guard.js";
|
|
6
6
|
import type { PwaConfig } from "./pwa/generate.js";
|
|
7
7
|
/**
|
|
8
|
-
* The starting shape the front-end takes. Not a fork
|
|
8
|
+
* The starting shape the front-end takes. Not a fork—each archetype is a preset
|
|
9
9
|
* of defaults (which sections/modules are on, nav shape) that the site then tunes.
|
|
10
10
|
* `marketing` = the lean brochure floor (louise-web, no commerce); `storefront` =
|
|
11
11
|
* DTC shop (coracle); `wholesale` = B2B/private-label (ghostfire); `portfolio` =
|
|
@@ -13,7 +13,7 @@ import type { PwaConfig } from "./pwa/generate.js";
|
|
|
13
13
|
*/
|
|
14
14
|
export type Archetype = "marketing" | "storefront" | "wholesale" | "portfolio";
|
|
15
15
|
/**
|
|
16
|
-
* The section vocabulary
|
|
16
|
+
* The section vocabulary—the editable home page is an ordered list of these,
|
|
17
17
|
* top to bottom.
|
|
18
18
|
*
|
|
19
19
|
* DERIVED from the section catalog, not hand-written (#277). It used to be its
|
|
@@ -30,42 +30,42 @@ export type SectionKind = keyof typeof astroidSectionCatalog;
|
|
|
30
30
|
/**
|
|
31
31
|
* Each archetype's default home-page sections.
|
|
32
32
|
*
|
|
33
|
-
* Lives here, in TypeScript, rather than in `create-astroid`'s plain JS
|
|
33
|
+
* Lives here, in TypeScript, rather than in `create-astroid`'s plain JS—the
|
|
34
34
|
* other half of #277. As a JS object literal it could name a section that
|
|
35
35
|
* didn't exist and nothing would say so; typed against {@link SectionKind}
|
|
36
36
|
* (itself derived from the catalog) a stale name is a compile error, and CI
|
|
37
37
|
* type-checks this package.
|
|
38
38
|
*
|
|
39
|
-
* The four kinds this used to name
|
|
40
|
-
*
|
|
39
|
+
* The four kinds this used to name—`marquee`, `featured`, `story`, `visit`—had
|
|
40
|
+
* no catalog entry or component and could never render. Each is replaced by
|
|
41
41
|
* the real section that does its job: a marquee is a `banner`, curated picks
|
|
42
42
|
* are a `productGrid`, a brand-origin block is `aboutIntro`, and "visit" is
|
|
43
43
|
* exactly `locationHours`.
|
|
44
44
|
*/
|
|
45
45
|
export declare const ASTROID_ARCHETYPE_SECTIONS: Record<Archetype, SectionKind[]>;
|
|
46
46
|
/**
|
|
47
|
-
* Optional capabilities the site switches on. Pluggable, not core
|
|
47
|
+
* Optional capabilities the site switches on. Pluggable, not core—a portfolio
|
|
48
48
|
* site runs none of the commerce ones.
|
|
49
49
|
*
|
|
50
50
|
* **Every value here is read by something.** The union used to also name
|
|
51
51
|
* `orderTracking`, `subscriptions`, `giftCards`, and `privateLabel`, none of
|
|
52
52
|
* which had a single consumer anywhere in the package: setting one type-checked,
|
|
53
|
-
* passed validation, and did nothing at all
|
|
53
|
+
* passed validation, and did nothing at all—no scaffold, no CSP origin, no
|
|
54
54
|
* rate rule, no table. A config surface that accepts a setting it ignores is
|
|
55
55
|
* worse than a smaller one, because the only way to discover the truth is to
|
|
56
56
|
* deploy and notice the absence.
|
|
57
57
|
*
|
|
58
58
|
* They are removed rather than left as TODOs. `orderTracking` in particular has
|
|
59
|
-
* a real implementation waiting
|
|
60
|
-
* generalized
|
|
59
|
+
* a real implementation waiting—`src/workflow/` is the ghostfire order tracker,
|
|
60
|
+
* generalized—but it is reached through `defineWorkflow`, not this flag, and
|
|
61
61
|
* pretending otherwise is what made the flag misleading. Re-add each one in the
|
|
62
62
|
* change that wires it.
|
|
63
63
|
*/
|
|
64
64
|
export type ModuleKind = "map" | "pwa" | "realtime" | "wholesaleInquiry";
|
|
65
|
-
/** Commerce backend
|
|
65
|
+
/** Commerce backend—mirrors Louise's provider set (louise-toolkit/commerce). */
|
|
66
66
|
export type CommerceProvider = "stripe" | "square" | "fourthwall";
|
|
67
67
|
export interface Theme {
|
|
68
|
-
/** Display name
|
|
68
|
+
/** Display name—the brand, used in nav, `<title>`, OG cards. */
|
|
69
69
|
name: string;
|
|
70
70
|
/** Path to the primary logo (media-library asset or a `/brand/*` file). */
|
|
71
71
|
logo?: string;
|
|
@@ -85,21 +85,21 @@ export interface Theme {
|
|
|
85
85
|
export interface Portal {
|
|
86
86
|
enabled: boolean;
|
|
87
87
|
/**
|
|
88
|
-
* @deprecated NOT IMPLEMENTED
|
|
88
|
+
* @deprecated NOT IMPLEMENTED—`defineAstroid` throws if this is set.
|
|
89
89
|
*
|
|
90
90
|
* It was meant to require a session for the whole site (a pre-launch client
|
|
91
91
|
* gallery), not just the account area, but nothing ever read it: the guard
|
|
92
92
|
* table is built from {@link Portal.routes} and `portalGuard` allows any
|
|
93
93
|
* unmatched path. Until it's wired, gate the site by naming the prefixes in
|
|
94
|
-
* `routes
|
|
94
|
+
* `routes`—that is the mechanism this would have been sugar for.
|
|
95
95
|
*/
|
|
96
96
|
gated?: boolean;
|
|
97
|
-
/** Modules exposed inside the account area (
|
|
97
|
+
/** Modules exposed inside the account area (for example, `wholesaleInquiry`, which
|
|
98
98
|
* adds the inquiries table even on an archetype that wouldn't have one). */
|
|
99
99
|
features?: ModuleKind[];
|
|
100
100
|
/**
|
|
101
101
|
* Roles a portal account can hold, first being the default for a new account.
|
|
102
|
-
* Default `["customer"]`. These are the portal's OWN roles
|
|
102
|
+
* Default `["customer"]`. These are the portal's OWN roles—entirely separate
|
|
103
103
|
* from the editor's `admin`, because the two auth instances don't share a
|
|
104
104
|
* user table.
|
|
105
105
|
*/
|
|
@@ -111,7 +111,7 @@ export interface Portal {
|
|
|
111
111
|
*/
|
|
112
112
|
routes?: PortalRoute[];
|
|
113
113
|
/**
|
|
114
|
-
* Where a portal user lands, per role
|
|
114
|
+
* Where a portal user lands, per role—used to bounce someone who reached an
|
|
115
115
|
* area they don't belong in. Default `/portal` for everyone.
|
|
116
116
|
*/
|
|
117
117
|
home?: Record<string, string>;
|
|
@@ -121,14 +121,14 @@ export interface Portal {
|
|
|
121
121
|
*/
|
|
122
122
|
signUp?: boolean;
|
|
123
123
|
/**
|
|
124
|
-
* Where the portal's Better Auth instance mounts
|
|
124
|
+
* Where the portal's Better Auth instance mounts—its own handler, separate
|
|
125
125
|
* from the editor's `/api/auth`. Default `/api/portal-auth`. Override when a
|
|
126
|
-
* site already ships a second instance at a different path (
|
|
126
|
+
* site already ships a second instance at a different path (for example, a shop
|
|
127
127
|
* account at `/api/shop-auth`) whose live cookies must not change.
|
|
128
128
|
*/
|
|
129
129
|
basePath?: string;
|
|
130
130
|
/**
|
|
131
|
-
* Cookie prefix for the portal instance
|
|
131
|
+
* Cookie prefix for the portal instance—MUST differ from the editor's
|
|
132
132
|
* (Better Auth's default), or signing into one instance signs you out of the
|
|
133
133
|
* other. Default `"portal"`. `defineAstroid` rejects a colliding value.
|
|
134
134
|
*/
|
|
@@ -144,14 +144,14 @@ export interface Portal {
|
|
|
144
144
|
export interface CommerceConfig {
|
|
145
145
|
/**
|
|
146
146
|
* Shorthand for a single-provider site. Assigns the provider to a role it can
|
|
147
|
-
* actually serve
|
|
147
|
+
* actually serve—`square`/`fourthwall` become the storefront, `stripe`
|
|
148
148
|
* becomes invoicing (its client has no catalog API).
|
|
149
149
|
*/
|
|
150
150
|
provider?: CommerceProvider;
|
|
151
151
|
/** Catalog, cart, checkout. Needs a provider with a catalog API. */
|
|
152
152
|
storefront?: CommerceProvider;
|
|
153
153
|
/**
|
|
154
|
-
* Invoices for work that isn't a catalog item
|
|
154
|
+
* Invoices for work that isn't a catalog item—commissions, originals.
|
|
155
155
|
* Independent of `storefront`: themidwestartist.com runs Stripe here and
|
|
156
156
|
* Fourthwall as the storefront, because neither can do the other's job.
|
|
157
157
|
*/
|
|
@@ -163,14 +163,14 @@ export interface CommerceConfig {
|
|
|
163
163
|
* Separate from `storefront` because they are genuinely different jobs and a
|
|
164
164
|
* site commonly runs both. themidwestartist.com sells print-on-demand merch
|
|
165
165
|
* through Fourthwall (`storefront`) while originals and self-stocked prints
|
|
166
|
-
* live in Square (`pos`) across several shops and galleries
|
|
166
|
+
* live in Square (`pos`) across several shops and galleries—one catalog per
|
|
167
167
|
* rail, neither able to do the other's job.
|
|
168
168
|
*
|
|
169
169
|
* What `pos` turns on that `storefront` does not: locations, per-location
|
|
170
170
|
* pricing, and per-location inventory.
|
|
171
171
|
*/
|
|
172
172
|
pos?: CommerceProvider;
|
|
173
|
-
/** The catalog mirror's shape
|
|
173
|
+
/** The catalog mirror's shape—its mode, table name, and owned columns. */
|
|
174
174
|
catalog?: CatalogMirrorConfig;
|
|
175
175
|
/** Square-specific options. Only meaningful when Square fills some role. */
|
|
176
176
|
square?: SquareCommerceConfig;
|
|
@@ -182,7 +182,7 @@ export interface SquareCommerceConfig {
|
|
|
182
182
|
* `"single"` (the default) is the ordinary case: one location, its id supplied
|
|
183
183
|
* once as `SQUARE_LOCATION_ID`, and every order placed against it.
|
|
184
184
|
*
|
|
185
|
-
* `"multi"` is the multi-merchant model
|
|
185
|
+
* `"multi"` is the multi-merchant model—each merchant is a Location, and the
|
|
186
186
|
* id comes from the *request* (which merchant's storefront is this?) rather
|
|
187
187
|
* than from the environment. Setting it stops Astroid requiring
|
|
188
188
|
* `SQUARE_LOCATION_ID`: a single ambient location id is not merely unnecessary
|
|
@@ -199,8 +199,8 @@ export interface QueuesConfig {
|
|
|
199
199
|
*/
|
|
200
200
|
enabled?: boolean;
|
|
201
201
|
/**
|
|
202
|
-
* Cron for the safety-net re-sync, or `false` for none. Webhooks get missed
|
|
203
|
-
*
|
|
202
|
+
* Cron for the safety-net re-sync, or `false` for none. Webhooks get missed—a
|
|
203
|
+
* provider outage, a deploy mid-delivery, a DLQ'd message—and without a
|
|
204
204
|
* periodic re-sync the site serves stale data until someone notices. Default
|
|
205
205
|
* hourly.
|
|
206
206
|
*/
|
|
@@ -218,13 +218,13 @@ export interface QueuesConfig {
|
|
|
218
218
|
*
|
|
219
219
|
* Declaring it here rather than by hand is what keeps `wrangler.jsonc` and the
|
|
220
220
|
* generated `scheduled` dispatch in agreement. Adding a cron to `triggers.crons`
|
|
221
|
-
* alone produces a trigger Cloudflare fires and the dispatch never matches
|
|
222
|
-
*
|
|
221
|
+
* alone produces a trigger Cloudflare fires and the dispatch never matches—unreachable
|
|
222
|
+
* code that costs an invocation and does nothing, with no error
|
|
223
223
|
* anywhere. Adding it in the dashboard instead drifts from the config that is
|
|
224
224
|
* supposed to describe the deploy.
|
|
225
225
|
*/
|
|
226
226
|
/**
|
|
227
|
-
* Serve `*.example.com` from this one Worker
|
|
227
|
+
* Serve `*.example.com` from this one Worker—scoped views of **this brand's**
|
|
228
228
|
* data, narrowed by host. A per-merchant storefront, a per-client gallery.
|
|
229
229
|
*
|
|
230
230
|
* Not multi-brand: see the note at the top of this file. Same theme, same
|
|
@@ -232,26 +232,26 @@ export interface QueuesConfig {
|
|
|
232
232
|
*
|
|
233
233
|
* Astroid provides only the plumbing that cannot live in a site: the wildcard
|
|
234
234
|
* Worker route (which `hosts` cannot express) and the one middleware file Astro
|
|
235
|
-
* permits. **Everything that decides anything stays yours
|
|
235
|
+
* permits. **Everything that decides anything stays yours**—what a label maps
|
|
236
236
|
* to, whether the lookup is cached, and what an unknown host should do. Those
|
|
237
237
|
* live in the scaffolded `src/tenancy.ts`, which is yours to edit.
|
|
238
238
|
*/
|
|
239
239
|
export interface TenancyConfig {
|
|
240
240
|
/**
|
|
241
|
-
* The wildcard host,
|
|
242
|
-
* (`{ pattern, zone_name }`), never `custom_domain: true
|
|
241
|
+
* The wildcard host, for example, `"*.example.com"`. Emitted as a **zone route**
|
|
242
|
+
* (`{ pattern, zone_name }`), never `custom_domain: true`—a wildcard cannot
|
|
243
243
|
* be a custom domain, which is exactly why `hosts` cannot express this.
|
|
244
244
|
*/
|
|
245
245
|
hostPattern: string;
|
|
246
246
|
/**
|
|
247
247
|
* The Cloudflare zone the pattern belongs to. Defaults to `hostPattern` minus
|
|
248
248
|
* its leading `*.`, which is right whenever the wildcard sits directly under
|
|
249
|
-
* the zone apex. Set it explicitly for a deeper pattern
|
|
249
|
+
* the zone apex. Set it explicitly for a deeper pattern—`*.shop.example.com`
|
|
250
250
|
* is served by the `example.com` zone, not a `shop.example.com` one.
|
|
251
251
|
*/
|
|
252
252
|
zone?: string;
|
|
253
253
|
/**
|
|
254
|
-
* Labels that are **not** tenants
|
|
254
|
+
* Labels that are **not** tenants—`www`, `admin`, `studio`, `api`. These
|
|
255
255
|
* skip the tenant lookup entirely and render the ordinary site.
|
|
256
256
|
*
|
|
257
257
|
* Declared here rather than in the seam because the generated middleware needs
|
|
@@ -264,27 +264,27 @@ export interface TenancyConfig {
|
|
|
264
264
|
* Internal path prefix a tenant request is rewritten to. Default `"/t"`, so
|
|
265
265
|
* `acme.example.com/prints` renders `/t/acme/prints`.
|
|
266
266
|
*
|
|
267
|
-
* The visitor's URL never changes
|
|
267
|
+
* The visitor's URL never changes—this is an internal rewrite, so links
|
|
268
268
|
* built from `Astro.url` stay public and correct.
|
|
269
269
|
*/
|
|
270
270
|
rewritePrefix?: string;
|
|
271
271
|
/**
|
|
272
272
|
* First-party apps on their own labels, mapped to the internal path prefix
|
|
273
|
-
* each serves from
|
|
273
|
+
* each serves from—`{ studio: "/studio" }` serves
|
|
274
274
|
* `studio.example.com/<path>` from `src/pages/studio/<path>`.
|
|
275
275
|
*
|
|
276
276
|
* This is the missing half of {@link PwaConfig.emitDir}'s subdomain story:
|
|
277
|
-
* an app label is not a tenant (there is no lookup
|
|
277
|
+
* an app label is not a tenant (there is no lookup—the studio exists
|
|
278
278
|
* whether or not any tenant does) and not merely reserved (a reserved label
|
|
279
279
|
* renders the ordinary site, which turns the admin host into a second copy
|
|
280
280
|
* of the marketing homepage). It is a static rewrite, decided at config time.
|
|
281
281
|
*
|
|
282
282
|
* An app label is implicitly reserved: `tenantLabel` never offers it to
|
|
283
|
-
* `resolveTenant`, and listing it in `reserved` too is refused
|
|
283
|
+
* `resolveTenant`, and listing it in `reserved` too is refused—one list
|
|
284
284
|
* per fact, or the two drift.
|
|
285
285
|
*
|
|
286
286
|
* Same internal-rewrite semantics as tenants: the visitor's URL never
|
|
287
|
-
* changes, and the path form stays reachable on the apex
|
|
287
|
+
* changes, and the path form stays reachable on the apex—which is what
|
|
288
288
|
* makes local dev work, since `wrangler dev` cannot serve subdomains.
|
|
289
289
|
*/
|
|
290
290
|
apps?: Record<string, string>;
|
|
@@ -292,7 +292,7 @@ export interface TenancyConfig {
|
|
|
292
292
|
* What a syntactically-valid tenant host whose label resolves to NOTHING
|
|
293
293
|
* (`resolveTenant` returned `null`) should get.
|
|
294
294
|
*
|
|
295
|
-
* `"fallthrough"` (the default) renders the ordinary site
|
|
295
|
+
* `"fallthrough"` (the default) renders the ordinary site—which means a
|
|
296
296
|
* stranger who points a CNAME at your zone gets your homepage. `"404"` emits
|
|
297
297
|
* a guard that refuses the request instead, which is the right answer the
|
|
298
298
|
* moment tenant hosts are commercial surfaces: an unknown storefront must be
|
|
@@ -302,7 +302,7 @@ export interface TenancyConfig {
|
|
|
302
302
|
* Either way the decision stays visible in config rather than buried in the
|
|
303
303
|
* seam: `resolveTenant` decides *what exists*; this decides what not-existing
|
|
304
304
|
* means. Reserved labels, app labels, the apex, and off-pattern hosts are
|
|
305
|
-
* never affected
|
|
305
|
+
* never affected—they aren't tenant candidates at all.
|
|
306
306
|
*/
|
|
307
307
|
unknown?: "fallthrough" | "404";
|
|
308
308
|
/**
|
|
@@ -311,13 +311,13 @@ export interface TenancyConfig {
|
|
|
311
311
|
* The rewrite exists to choose which PAGE renders for a host. An API route
|
|
312
312
|
* is not a page: its address is absolute, chosen by the client that calls
|
|
313
313
|
* it, and it reads the host from `locals.tenant` rather than from its own
|
|
314
|
-
* path. Rewriting it moves it somewhere no route matches
|
|
314
|
+
* path. Rewriting it moves it somewhere no route matches—and on an app
|
|
315
315
|
* host with a catch-all page, somewhere much worse than a 404: the page
|
|
316
316
|
* catch-all answers, so `fetch("/api/…")` gets HTML (or a redirect to a
|
|
317
317
|
* sign-in) instead of JSON, and every data load on that host silently fails
|
|
318
318
|
* while the same code works on the apex.
|
|
319
319
|
*
|
|
320
|
-
* That is not hypothetical
|
|
320
|
+
* That is not hypothetical—it is why this default exists (found on
|
|
321
321
|
* themidwestartist.com's studio, where the whole admin app loaded and then
|
|
322
322
|
* fetched nothing).
|
|
323
323
|
*
|
|
@@ -328,7 +328,7 @@ export interface TenancyConfig {
|
|
|
328
328
|
rewriteExclude?: string[];
|
|
329
329
|
}
|
|
330
330
|
export interface AstroidCron {
|
|
331
|
-
/** Standard 5-field cron, UTC
|
|
331
|
+
/** Standard 5-field cron, UTC—for example, `"*/15 * * * *"`. */
|
|
332
332
|
expression: string;
|
|
333
333
|
/**
|
|
334
334
|
* The queue message this trigger sends. **Enqueued, never run inline**, so the
|
|
@@ -350,14 +350,14 @@ export interface SeoConfig {
|
|
|
350
350
|
/**
|
|
351
351
|
* schema.org `@type` for the business node in the JSON-LD graph. Defaults to
|
|
352
352
|
* the archetype's broad type (see `ARCHETYPE_BUSINESS_TYPE`); set a more
|
|
353
|
-
* specific subtype whenever you know one
|
|
354
|
-
* `"ArtGallery"`, `"HomeAndConstructionBusiness"
|
|
353
|
+
* specific subtype whenever you know one—`"CafeOrCoffeeShop"`,
|
|
354
|
+
* `"ArtGallery"`, `"HomeAndConstructionBusiness"`—since a narrower type is
|
|
355
355
|
* strictly better for rich results.
|
|
356
356
|
*/
|
|
357
357
|
businessType?: string;
|
|
358
358
|
/** `@handle` for Twitter/X card attribution. */
|
|
359
359
|
twitterHandle?: string;
|
|
360
|
-
/** Open Graph locale,
|
|
360
|
+
/** Open Graph locale, for example, `"en_US"`. */
|
|
361
361
|
locale?: string;
|
|
362
362
|
}
|
|
363
363
|
export interface SecurityConfig {
|
|
@@ -391,8 +391,8 @@ export interface SettingsConfig {
|
|
|
391
391
|
* Override the editable base `site_settings` columns. Defaults to Astroid's
|
|
392
392
|
* standard set (`ASTROID_SETTINGS_COLUMNS`). A **custom-heavy** site whose
|
|
393
393
|
* settings shape doesn't align with the base column names keeps everything in
|
|
394
|
-
* `custom` by passing `[]
|
|
395
|
-
* column name (
|
|
394
|
+
* `custom` by passing `[]`—otherwise a key that happens to match a base
|
|
395
|
+
* column name (for example, `contactEmail`) would route to that column instead of
|
|
396
396
|
* `custom`, where the site's render reads it.
|
|
397
397
|
*/
|
|
398
398
|
columns?: string[];
|
|
@@ -405,8 +405,8 @@ export interface SettingsConfig {
|
|
|
405
405
|
* hours table, ui strings, shop/order config) lists their top-level keys here.
|
|
406
406
|
*/
|
|
407
407
|
customKeys?: string[];
|
|
408
|
-
/** Extra media-library image keys beyond the base logo/favicon/OG defaults
|
|
409
|
-
|
|
408
|
+
/** Extra media-library image keys beyond the base logo/favicon/OG defaults—*
|
|
409
|
+
settings values validated as media-library URLs on write. */
|
|
410
410
|
imageKeys?: string[];
|
|
411
411
|
}
|
|
412
412
|
/** Media-library upload policy. */
|
|
@@ -415,7 +415,7 @@ export interface MediaConfig {
|
|
|
415
415
|
* Largest accepted upload, in bytes. Default 10 MB (louise-toolkit's
|
|
416
416
|
* `DEFAULT_MAX_BYTES`).
|
|
417
417
|
*
|
|
418
|
-
* Raise it when the masters ARE the product
|
|
418
|
+
* Raise it when the masters ARE the product—a photographer's or painter's
|
|
419
419
|
* portfolio uploads 40 MB camera files and only ever serves Cloudflare-
|
|
420
420
|
* resized derivatives, so the master's size costs storage, not page weight.
|
|
421
421
|
*
|
|
@@ -428,21 +428,21 @@ export interface MediaConfig {
|
|
|
428
428
|
}
|
|
429
429
|
export interface DeployConfig {
|
|
430
430
|
platform: "cloudflare";
|
|
431
|
-
/** Media base for R2 + `cf-image` resizing
|
|
431
|
+
/** Media base for R2 + `cf-image` resizing—matches Louise's media route
|
|
432
432
|
* (`media.<brand>/cdn-cgi/image`). Default `"/media"`. */
|
|
433
433
|
mediaBase?: string;
|
|
434
434
|
}
|
|
435
435
|
export interface AstroidConfig {
|
|
436
436
|
/**
|
|
437
|
-
* Stable project slug
|
|
437
|
+
* Stable project slug—the worker/D1/R2 base name and default subdomain (for example,
|
|
438
438
|
* `"coracle"`). Required and non-empty; it drives the generated binding names.
|
|
439
439
|
*/
|
|
440
440
|
key: string;
|
|
441
|
-
/**
|
|
441
|
+
/** Hostnames this site serves (prod + preview), for custom-domain routes. */
|
|
442
442
|
hosts?: string[];
|
|
443
443
|
/**
|
|
444
444
|
* Serve a wildcard host from this Worker, mapping each subdomain to an
|
|
445
|
-
* internal path prefix. See {@link TenancyConfig}
|
|
445
|
+
* internal path prefix. See {@link TenancyConfig}—and note it is an
|
|
446
446
|
* *audiences* axis, not multi-brand.
|
|
447
447
|
*/
|
|
448
448
|
tenancy?: TenancyConfig;
|
|
@@ -455,8 +455,8 @@ export interface AstroidConfig {
|
|
|
455
455
|
/**
|
|
456
456
|
* A site-provided section catalog that REPLACES the built-in one for
|
|
457
457
|
* SERVER-side validation + sanitization of `pages.sections` (the generated
|
|
458
|
-
* pages route + versions route). A site with bespoke section designs
|
|
459
|
-
* `.astro` components and field defs (coracle's 13 sections)
|
|
458
|
+
* pages route + versions route). A site with bespoke section designs—its own
|
|
459
|
+
* `.astro` components and field defs (coracle's 13 sections)—registers them
|
|
460
460
|
* here so writes to its custom `_type`s validate instead of 422-ing against the
|
|
461
461
|
* built-in vocabulary. The on-canvas editor already uses the site's catalog
|
|
462
462
|
* (its `mountSections` call passes it); this closes the server half so both
|
|
@@ -464,13 +464,13 @@ export interface AstroidConfig {
|
|
|
464
464
|
*/
|
|
465
465
|
sectionCatalog?: SectionCatalog;
|
|
466
466
|
/**
|
|
467
|
-
* The site's catalog of BLOCK types (ADR 0005)
|
|
467
|
+
* The site's catalog of BLOCK types (ADR 0005)—the block-level analogue of
|
|
468
468
|
* {@link sectionCatalog}, and required for any section whose def declares a
|
|
469
469
|
* `blocks` policy.
|
|
470
470
|
*
|
|
471
471
|
* Without it the server has no field shape to check a block against, so
|
|
472
472
|
* `validateSections` rejects every block `_type` as unknown and a block-bearing
|
|
473
|
-
* write
|
|
473
|
+
* write returns 422—the on-canvas block toolbar appears to work and then nothing
|
|
474
474
|
* saves. It also gates block rich-text **sanitization**: block fields are only
|
|
475
475
|
* scrubbed when their def is resolvable here.
|
|
476
476
|
*
|
|
@@ -499,10 +499,10 @@ export interface AstroidConfig {
|
|
|
499
499
|
seo?: SeoConfig;
|
|
500
500
|
/** Additions to the rate-limit rules + CSP origins Astroid derives. */
|
|
501
501
|
security?: SecurityConfig;
|
|
502
|
-
/** Site-specific editable settings
|
|
502
|
+
/** Site-specific editable settings—extra `custom` keys + image keys on top
|
|
503
503
|
* of Astroid's base `site_settings` columns. */
|
|
504
504
|
settings?: SettingsConfig;
|
|
505
|
-
/** Media-library upload policy (
|
|
505
|
+
/** Media-library upload policy (for example, a larger `maxUploadBytes`). */
|
|
506
506
|
media?: MediaConfig;
|
|
507
507
|
/**
|
|
508
508
|
* Force the contact form + `inquiries` table on or off. Omit to detect from
|
package/dist/config.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// `defineAstroid
|
|
3
|
+
// `defineAstroid`—the Astroid project configuration surface.
|
|
4
4
|
//
|
|
5
5
|
// Astroid is the opinionated layer over Louise Toolkit + Astro. A site's whole
|
|
6
|
-
// shape
|
|
7
|
-
// modules
|
|
6
|
+
// shape—its brand + theme + editable home, its commerce backend, its optional
|
|
7
|
+
// modules—collapses into ONE typed config here. Astroid consumes it to generate
|
|
8
8
|
// the Louise wiring (worker routes, middleware, Drizzle schema, theme tokens) a
|
|
9
9
|
// site would otherwise hand-write per repo.
|
|
10
10
|
//
|
|
11
11
|
// ONE brand per project. Every site Astroid targets (coracle.coffee,
|
|
12
12
|
// ghostfire.coffee, themidwestartist.com, louise-web) serves a single brand from a
|
|
13
13
|
// single deploy. The axis that genuinely multiplexes is *editors* (Louise's org
|
|
14
|
-
// plugin, #100) and *audiences
|
|
15
|
-
// per-merchant storefront on its own subdomain (`tenancy`)
|
|
14
|
+
// plugin, #100) and *audiences*—a gated portal alongside the public site, or a
|
|
15
|
+
// per-merchant storefront on its own subdomain (`tenancy`)—not brands. So all
|
|
16
16
|
// of them live here as options on the one brand, not as a `brands[]` array.
|
|
17
17
|
//
|
|
18
18
|
// `tenancy` is worth being precise about, because "serves many hosts" sounds like
|
|
19
19
|
// the thing this paragraph rules out and isn't. It serves scoped VIEWS OF ONE
|
|
20
|
-
// BRAND'S DATA
|
|
20
|
+
// BRAND'S DATA—the same theme, the same catalog, the same editors—narrowed by
|
|
21
21
|
// host. It is the portal's axis, one step further out. A second brand still means
|
|
22
22
|
// a second project.
|
|
23
23
|
//
|
|
24
24
|
// The vocabulary below is not invented: `Archetype`, `SectionKind`, and
|
|
25
|
-
// `ModuleKind` are extracted from the real sites Astroid targets
|
|
25
|
+
// `ModuleKind` are extracted from the real sites Astroid targets—a storefront
|
|
26
26
|
// (coracle), a wholesale front (ghostfire), an artist portfolio (megbowen), and a
|
|
27
27
|
// plain marketing baseline (louise-web).
|
|
28
28
|
import { assertAuthIsolation } from "./auth/index.js";
|
|
@@ -37,14 +37,14 @@ import { ASTROID_HEALTH_CRON, astroidCron, astroidUsesQueues } from "./queues/me
|
|
|
37
37
|
/**
|
|
38
38
|
* Each archetype's default home-page sections.
|
|
39
39
|
*
|
|
40
|
-
* Lives here, in TypeScript, rather than in `create-astroid`'s plain JS
|
|
40
|
+
* Lives here, in TypeScript, rather than in `create-astroid`'s plain JS—the
|
|
41
41
|
* other half of #277. As a JS object literal it could name a section that
|
|
42
42
|
* didn't exist and nothing would say so; typed against {@link SectionKind}
|
|
43
43
|
* (itself derived from the catalog) a stale name is a compile error, and CI
|
|
44
44
|
* type-checks this package.
|
|
45
45
|
*
|
|
46
|
-
* The four kinds this used to name
|
|
47
|
-
*
|
|
46
|
+
* The four kinds this used to name—`marquee`, `featured`, `story`, `visit`—had
|
|
47
|
+
* no catalog entry or component and could never render. Each is replaced by
|
|
48
48
|
* the real section that does its job: a marquee is a `banner`, curated picks
|
|
49
49
|
* are a `productGrid`, a brand-origin block is `aboutIntro`, and "visit" is
|
|
50
50
|
* exactly `locationHours`.
|
|
@@ -79,8 +79,8 @@ export const ASTROID_ARCHETYPE_SECTIONS = {
|
|
|
79
79
|
* A duplicate expression is the sharper one: the generated dispatch matches on
|
|
80
80
|
* `controller.cron` in order, so a custom trigger colliding with a derived one
|
|
81
81
|
* (or another custom one) never reaches its own branch. Cloudflare fires it, the
|
|
82
|
-
* first branch handles it, and the config reads as though both are live
|
|
83
|
-
*
|
|
82
|
+
* first branch handles it, and the config reads as though both are live—exactly
|
|
83
|
+
* the unreachable-trigger failure `config.crons` exists to prevent.
|
|
84
84
|
*/
|
|
85
85
|
function assertCrons(config) {
|
|
86
86
|
const crons = config.crons ?? [];
|
|
@@ -114,7 +114,7 @@ function assertCrons(config) {
|
|
|
114
114
|
*
|
|
115
115
|
* All three are cheap to state and expensive to discover: two surface as a
|
|
116
116
|
* wrangler deploy error naming a zone or a pattern rather than the config that
|
|
117
|
-
* produced it, and the third never surfaces at all
|
|
117
|
+
* produced it, and the third never surfaces at all—it just serves the wrong
|
|
118
118
|
* page.
|
|
119
119
|
*/
|
|
120
120
|
function assertTenancy(config) {
|
|
@@ -134,7 +134,7 @@ function assertTenancy(config) {
|
|
|
134
134
|
throw new AstroidConfigError(`\`tenancy.hostPattern\` "${pattern}" has no domain after the wildcard`);
|
|
135
135
|
}
|
|
136
136
|
// A wildcard does NOT match its own apex, so the apex needs its own route.
|
|
137
|
-
// Without one it
|
|
137
|
+
// Without one it returns 404—and the symptom is "the marketing site is down"
|
|
138
138
|
// immediately after enabling a feature that reads like it only adds hosts.
|
|
139
139
|
if (!(config.hosts ?? []).some((host) => host.toLowerCase() === apex.toLowerCase())) {
|
|
140
140
|
throw new AstroidConfigError(`\`tenancy.hostPattern\` is "${pattern}", but "${apex}" is not in \`hosts\`. ` +
|
|
@@ -161,7 +161,7 @@ function assertTenancy(config) {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
/** Cloudflare rejects a request body over 100 MB at the edge, before any Worker
|
|
164
|
-
* handler runs
|
|
164
|
+
* handler runs—so an upload limit above it can never be honoured, and the
|
|
165
165
|
* failure arrives as an opaque edge error rather than the route's own 413. */
|
|
166
166
|
const WORKERS_MAX_REQUEST_BODY_BYTES = 100 * 1024 * 1024;
|
|
167
167
|
function assertMediaConfig(media) {
|
|
@@ -191,15 +191,15 @@ export function defineAstroid(config) {
|
|
|
191
191
|
// Fourthwall, a storefront over Stripe) fails here rather than at runtime on
|
|
192
192
|
// the first invoice, as a missing function.
|
|
193
193
|
assertCommerceRoles(config.commerce);
|
|
194
|
-
// A media limit above the platform's own body cap is unhonourable
|
|
194
|
+
// A media limit above the platform's own body cap is unhonourable—reject it
|
|
195
195
|
// here rather than let an editor watch a 120 MB upload die at the edge.
|
|
196
196
|
assertMediaConfig(config.media);
|
|
197
197
|
// A portal is a SECOND Better Auth instance beside the editor's. Reject any
|
|
198
198
|
// isolation that would collide with the editor on the same origin (a shared
|
|
199
199
|
// cookie prefix silently cross-signs-out; a shared table prefix merges the two
|
|
200
|
-
// user tables)
|
|
200
|
+
// user tables)—the intermittent-prod failure the fixed defaults prevent.
|
|
201
201
|
assertAuthIsolation(config);
|
|
202
|
-
// `portal.gated` is declared and resolved but read by NOTHING
|
|
202
|
+
// `portal.gated` is declared and resolved but read by NOTHING—the guard
|
|
203
203
|
// table is built from `portal.routes` alone, and `portalGuard` allows any
|
|
204
204
|
// unmatched path. So a site that set it believed the whole site sat behind a
|
|
205
205
|
// login (a pre-launch client gallery) while every page outside /portal was
|
package/dist/email/inquiry.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { SecretSource } from "../secrets.js";
|
|
|
3
3
|
import type { EmailSender } from "./send.js";
|
|
4
4
|
import { type DeliveryResult } from "./send.js";
|
|
5
5
|
import { type MailThemeOverrides } from "./theme.js";
|
|
6
|
-
/** The bindings the inquiry hook reads. All optional
|
|
6
|
+
/** The bindings the inquiry hook reads. All optional—an unprovisioned mail
|
|
7
7
|
* setup logs instead of sending, per the dormant-until-provisioned convention. */
|
|
8
8
|
export interface AstroidMailEnv {
|
|
9
9
|
/** Cloudflare Email Sending binding. */
|
|
@@ -11,7 +11,7 @@ export interface AstroidMailEnv {
|
|
|
11
11
|
/**
|
|
12
12
|
* Envelope sender; its domain must be onboarded for Email Sending. A
|
|
13
13
|
* `SecretSource` rather than a plain string so a Secrets Store binding works
|
|
14
|
-
* here too
|
|
14
|
+
* here too—and so the placeholder sentinel reads as unconfigured.
|
|
15
15
|
*/
|
|
16
16
|
MAIL_FROM?: SecretSource;
|
|
17
17
|
/** Where owner notifications go. Also the first editor's address. */
|
package/dist/email/inquiry.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// the submission is already durable, so mail is a notification of something that
|
|
8
8
|
// already happened and can fail without the visitor ever knowing.
|
|
9
9
|
//
|
|
10
|
-
// Two messages, not one
|
|
10
|
+
// Two messages, not one—every site converged on the pair. The owner needs the
|
|
11
11
|
// message; the visitor needs to know it arrived, because a contact form with no
|
|
12
12
|
// acknowledgement is indistinguishable from one that's broken.
|
|
13
13
|
import { resolveMailer, sendTransactional } from "./send.js";
|
package/dist/email/send.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface MailerStatus {
|
|
|
18
18
|
/** True only when a binding AND a real sender address are both present. */
|
|
19
19
|
configured: boolean;
|
|
20
20
|
values: ModuleSecrets<"MAIL_FROM">["values"];
|
|
21
|
-
/** What's unprovisioned
|
|
21
|
+
/** What's unprovisioned—secret names and/or `"EMAIL"`. */
|
|
22
22
|
missing: string[];
|
|
23
23
|
/** Whether an Email Sending binding is present at all. */
|
|
24
24
|
hasBinding: boolean;
|
|
@@ -34,7 +34,7 @@ export interface MailerEnv {
|
|
|
34
34
|
* Both halves are required, and for the same reason: a binding with no sender
|
|
35
35
|
* address can't build an envelope, and a sender address with no binding has
|
|
36
36
|
* nothing to send through. Either one missing means log-and-continue, which
|
|
37
|
-
* under `wrangler dev` (no EMAIL binding at all) is the normal case
|
|
37
|
+
* under `wrangler dev` (no EMAIL binding at all) is the normal case—and the
|
|
38
38
|
* reason the magic-link flow is still workable locally.
|
|
39
39
|
*/
|
|
40
40
|
export declare function resolveMailerStatus(env: MailerEnv): Promise<MailerStatus>;
|
|
@@ -54,7 +54,7 @@ export declare function resolveMailer(env: MailerEnv, overrides?: Partial<Omit<M
|
|
|
54
54
|
export interface OutgoingMail {
|
|
55
55
|
to: string;
|
|
56
56
|
content: MailContent;
|
|
57
|
-
/** Reply-To
|
|
57
|
+
/** Reply-To—for an inquiry notification, the visitor's own address, so the
|
|
58
58
|
* owner can just hit reply. */
|
|
59
59
|
replyTo?: string;
|
|
60
60
|
}
|
|
@@ -64,7 +64,7 @@ export interface DeliveryResult {
|
|
|
64
64
|
subject: string;
|
|
65
65
|
delivered: boolean;
|
|
66
66
|
messageId?: string;
|
|
67
|
-
/** Why it wasn't delivered
|
|
67
|
+
/** Why it wasn't delivered—`"not-configured"`, `"log-only"`, or the error. */
|
|
68
68
|
reason?: string;
|
|
69
69
|
}
|
|
70
70
|
export interface MailerOptions {
|
package/dist/email/send.js
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// Transactional mail in this stack is always store-and-forward: the inquiry row
|
|
6
6
|
// is already in D1, the account already exists. Mail is the *notification* of
|
|
7
7
|
// something that happened, so a mail failure must never fail the request that
|
|
8
|
-
// caused it
|
|
8
|
+
// caused it—and never throw into a `waitUntil` where it becomes an unhandled
|
|
9
9
|
// rejection. Every path here resolves.
|
|
10
10
|
//
|
|
11
11
|
// The second job is the dev story. There is no EMAIL binding under `wrangler
|
|
12
|
-
// dev`, and the single most common local task is "click the magic link"
|
|
12
|
+
// dev`, and the single most common local task is "click the magic link"—so an
|
|
13
13
|
// unconfigured mailer LOGS the message instead of silently dropping it, and it
|
|
14
14
|
// logs the plaintext body, which is where the link is. That is the whole reason
|
|
15
15
|
// every template renders a text alternative.
|
|
@@ -32,7 +32,7 @@ export const EMAIL_SECRET_NAMES = ["MAIL_FROM"];
|
|
|
32
32
|
* Both halves are required, and for the same reason: a binding with no sender
|
|
33
33
|
* address can't build an envelope, and a sender address with no binding has
|
|
34
34
|
* nothing to send through. Either one missing means log-and-continue, which
|
|
35
|
-
* under `wrangler dev` (no EMAIL binding at all) is the normal case
|
|
35
|
+
* under `wrangler dev` (no EMAIL binding at all) is the normal case—and the
|
|
36
36
|
* reason the magic-link flow is still workable locally.
|
|
37
37
|
*/
|
|
38
38
|
export async function resolveMailerStatus(env) {
|
|
@@ -67,11 +67,11 @@ export async function resolveMailer(env, overrides = {}) {
|
|
|
67
67
|
/**
|
|
68
68
|
* The console rendering of an unsent message.
|
|
69
69
|
*
|
|
70
|
-
* The body is the whole point in dev
|
|
70
|
+
* The body is the whole point in dev—that's where a sign-in link actually is,
|
|
71
71
|
* and printing it is what lets you sign in with no mail provider configured.
|
|
72
72
|
*
|
|
73
73
|
* It is also a credential. `logOnly` turns on whenever `MAIL_FROM` is unset, and
|
|
74
|
-
* that can happen in PRODUCTION
|
|
74
|
+
* that can happen in PRODUCTION—a secret that didn't get set, or a Secrets
|
|
75
75
|
* Store read that failed. The body then went to `console.info`, which means
|
|
76
76
|
* `wrangler tail` and every Logpush sink, carrying live single-use magic links
|
|
77
77
|
* and password-reset URLs. Anyone with read access to observability could take
|
|
@@ -105,7 +105,7 @@ function describe(mail, reason, includeBody) {
|
|
|
105
105
|
/**
|
|
106
106
|
* Best-effort "are we in development?".
|
|
107
107
|
*
|
|
108
|
-
* Deliberately conservative
|
|
108
|
+
* Deliberately conservative—it decides whether a credential is printed, so an
|
|
109
109
|
* unknown environment must read as production. Workers has no `NODE_ENV`, so we
|
|
110
110
|
* look at the signals that do exist and let a caller override explicitly.
|
|
111
111
|
*/
|
|
@@ -176,7 +176,7 @@ export async function sendTransactional(options, mails) {
|
|
|
176
176
|
// A genuine delivery failure is LOGGED, not just returned.
|
|
177
177
|
//
|
|
178
178
|
// The result array was the only record of it, and the one caller that
|
|
179
|
-
// matters
|
|
179
|
+
// matters—the generated inquiry handler—discards it by design (the row
|
|
180
180
|
// is already durable, and the visitor must not see a 500 because the owner's
|
|
181
181
|
// notification bounced). So a dead Email Sending domain or an exhausted
|
|
182
182
|
// quota produced silence everywhere: a success page for the visitor, nothing
|