astroidjs 0.7.2 → 0.8.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.
- package/dist/config.d.ts +38 -0
- package/dist/tenancy/index.d.ts +13 -0
- package/dist/tenancy/index.js +17 -2
- package/dist/worker/generate.js +42 -18
- package/package.json +2 -2
package/dist/config.d.ts
CHANGED
|
@@ -288,6 +288,44 @@ export interface TenancyConfig {
|
|
|
288
288
|
* makes local dev work, since `wrangler dev` cannot serve subdomains.
|
|
289
289
|
*/
|
|
290
290
|
apps?: Record<string, string>;
|
|
291
|
+
/**
|
|
292
|
+
* What a syntactically-valid tenant host whose label resolves to NOTHING
|
|
293
|
+
* (`resolveTenant` returned `null`) should get.
|
|
294
|
+
*
|
|
295
|
+
* `"fallthrough"` (the default) renders the ordinary site — which means a
|
|
296
|
+
* stranger who points a CNAME at your zone gets your homepage. `"404"` emits
|
|
297
|
+
* a guard that refuses the request instead, which is the right answer the
|
|
298
|
+
* moment tenant hosts are commercial surfaces: an unknown storefront must be
|
|
299
|
+
* unambiguously *not a page*, not a copy of the marketing site under someone
|
|
300
|
+
* else's name.
|
|
301
|
+
*
|
|
302
|
+
* Either way the decision stays visible in config rather than buried in the
|
|
303
|
+
* seam: `resolveTenant` decides *what exists*; this decides what not-existing
|
|
304
|
+
* means. Reserved labels, app labels, the apex, and off-pattern hosts are
|
|
305
|
+
* never affected — they aren't tenant candidates at all.
|
|
306
|
+
*/
|
|
307
|
+
unknown?: "fallthrough" | "404";
|
|
308
|
+
/**
|
|
309
|
+
* Path prefixes the host rewrite must leave alone. Default `["/api"]`.
|
|
310
|
+
*
|
|
311
|
+
* The rewrite exists to choose which PAGE renders for a host. An API route
|
|
312
|
+
* is not a page: its address is absolute, chosen by the client that calls
|
|
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 — and on an app
|
|
315
|
+
* host with a catch-all page, somewhere much worse than a 404: the page
|
|
316
|
+
* catch-all answers, so `fetch("/api/…")` gets HTML (or a redirect to a
|
|
317
|
+
* sign-in) instead of JSON, and every data load on that host silently fails
|
|
318
|
+
* while the same code works on the apex.
|
|
319
|
+
*
|
|
320
|
+
* That is not hypothetical — it is why this default exists (found on
|
|
321
|
+
* themidwestartist.com's studio, where the whole admin app loaded and then
|
|
322
|
+
* fetched nothing).
|
|
323
|
+
*
|
|
324
|
+
* Set `[]` to rewrite everything, or add prefixes for other host-agnostic
|
|
325
|
+
* surfaces (`/_actions`, `/webhooks`). Matching is prefix-based on a path
|
|
326
|
+
* segment, so `/api` matches `/api/x` and `/api` but never `/apiary`.
|
|
327
|
+
*/
|
|
328
|
+
rewriteExclude?: string[];
|
|
291
329
|
}
|
|
292
330
|
export interface AstroidCron {
|
|
293
331
|
/** Standard 5-field cron, UTC — e.g. `"*/15 * * * *"`. */
|
package/dist/tenancy/index.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import type { AstroidConfig, TenancyConfig } from "../config.js";
|
|
2
2
|
/** Default internal prefix a tenant request is rewritten to. */
|
|
3
3
|
export declare const ASTROID_TENANT_PREFIX = "/t";
|
|
4
|
+
/** Path prefixes the host rewrite leaves alone unless a site says otherwise.
|
|
5
|
+
* See {@link TenancyConfig.rewriteExclude} for why `/api` is the default. */
|
|
6
|
+
export declare const ASTROID_REWRITE_EXCLUDE: string[];
|
|
7
|
+
/**
|
|
8
|
+
* Whether a path is host-agnostic and must render from its own address rather
|
|
9
|
+
* than the rewritten one.
|
|
10
|
+
*
|
|
11
|
+
* Segment-aware on purpose: a prefix of `/api` must match `/api` and `/api/x`
|
|
12
|
+
* but never `/apiary`, or a site could lose a page to a coincidental name.
|
|
13
|
+
*
|
|
14
|
+
* Exported and pure so a site can unit-test its own exclusion list.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isRewriteExcluded(pathname: string, exclude: string[]): boolean;
|
|
4
17
|
/**
|
|
5
18
|
* The Cloudflare zone a wildcard pattern belongs to.
|
|
6
19
|
*
|
package/dist/tenancy/index.js
CHANGED
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
// `src/tenancy.ts`.
|
|
11
11
|
/** Default internal prefix a tenant request is rewritten to. */
|
|
12
12
|
export const ASTROID_TENANT_PREFIX = "/t";
|
|
13
|
+
/** Path prefixes the host rewrite leaves alone unless a site says otherwise.
|
|
14
|
+
* See {@link TenancyConfig.rewriteExclude} for why `/api` is the default. */
|
|
15
|
+
export const ASTROID_REWRITE_EXCLUDE = ["/api"];
|
|
16
|
+
/**
|
|
17
|
+
* Whether a path is host-agnostic and must render from its own address rather
|
|
18
|
+
* than the rewritten one.
|
|
19
|
+
*
|
|
20
|
+
* Segment-aware on purpose: a prefix of `/api` must match `/api` and `/api/x`
|
|
21
|
+
* but never `/apiary`, or a site could lose a page to a coincidental name.
|
|
22
|
+
*
|
|
23
|
+
* Exported and pure so a site can unit-test its own exclusion list.
|
|
24
|
+
*/
|
|
25
|
+
export function isRewriteExcluded(pathname, exclude) {
|
|
26
|
+
return exclude.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix.replace(/\/$/, "")}/`));
|
|
27
|
+
}
|
|
13
28
|
/**
|
|
14
29
|
* The Cloudflare zone a wildcard pattern belongs to.
|
|
15
30
|
*
|
|
@@ -115,8 +130,8 @@ export function generateAstroidTenancy(config) {
|
|
|
115
130
|
" *",
|
|
116
131
|
" * `null` falls through to the ordinary site — which is a real choice, not a",
|
|
117
132
|
" * default: a stranger's subdomain then renders your homepage. If that is wrong",
|
|
118
|
-
|
|
119
|
-
" *
|
|
133
|
+
' * for this project, set `tenancy.unknown: "404"` in astroid.config.ts and the',
|
|
134
|
+
" * generated middleware refuses the host instead — unambiguously not a page.",
|
|
120
135
|
" *",
|
|
121
136
|
" * This runs on EVERY request to a tenant host, so a database lookup here is a",
|
|
122
137
|
" * query per request. Cache it — a module-scope Map is enough within an isolate,",
|
package/dist/worker/generate.js
CHANGED
|
@@ -17,7 +17,7 @@ import { astroidPortal } from "../portal/config.js";
|
|
|
17
17
|
import { ASTROID_HEALTH_CRON, astroidCron, astroidUsesQueues } from "../queues/messages.js";
|
|
18
18
|
import { ASTROID_EDIT_SESSION_CLASS, ASTROID_REALTIME_BINDING, usesRealtime, } from "../realtime/scaffold.js";
|
|
19
19
|
import { capturesInquiries } from "../schema/framework.js";
|
|
20
|
-
import { ASTROID_TENANT_PREFIX } from "../tenancy/index.js";
|
|
20
|
+
import { ASTROID_REWRITE_EXCLUDE, ASTROID_TENANT_PREFIX } from "../tenancy/index.js";
|
|
21
21
|
import { astroidEditorRoutePlan } from "./routes.js";
|
|
22
22
|
// Astroid's default editable site_settings surface — the columns the Settings
|
|
23
23
|
// panel may write, and which of them hold a media-library image URL.
|
|
@@ -462,6 +462,7 @@ export function generateAstroidMiddleware(config) {
|
|
|
462
462
|
const portal = astroidPortal(config);
|
|
463
463
|
const tenancy = config.tenancy;
|
|
464
464
|
const rewritePrefix = tenancy?.rewritePrefix ?? ASTROID_TENANT_PREFIX;
|
|
465
|
+
const rewriteExclude = tenancy?.rewriteExclude ?? ASTROID_REWRITE_EXCLUDE;
|
|
465
466
|
return [
|
|
466
467
|
"// Generated by astroidjs — do not hand-edit.",
|
|
467
468
|
"// The shared Louise middleware: rate-limit the unauthenticated POST surfaces,",
|
|
@@ -475,12 +476,11 @@ export function generateAstroidMiddleware(config) {
|
|
|
475
476
|
// two import statements for the same module is legal and reads as an
|
|
476
477
|
// oversight in a file nobody is supposed to hand-edit.
|
|
477
478
|
`import { ${[
|
|
479
|
+
...(tenancy && Object.keys(tenancy.apps ?? {}).length ? ["appPrefix"] : []),
|
|
478
480
|
...(portal ? ["astroidPortalGuardConfig"] : []),
|
|
479
481
|
"astroidRateRules",
|
|
480
482
|
...(portal ? ["guardResponse", "portalGuard", "resolvePortalSession"] : []),
|
|
481
|
-
...(tenancy
|
|
482
|
-
? [...(Object.keys(tenancy.apps ?? {}).length ? ["appPrefix"] : []), "tenantLabel"]
|
|
483
|
-
: []),
|
|
483
|
+
...(tenancy ? ["isRewriteExcluded", "tenantLabel"] : []),
|
|
484
484
|
].join(", ")} } from "astroidjs";`,
|
|
485
485
|
'import astroidConfig from "../astroid.config.js";',
|
|
486
486
|
...(tenancy
|
|
@@ -548,20 +548,36 @@ export function generateAstroidMiddleware(config) {
|
|
|
548
548
|
" },",
|
|
549
549
|
]
|
|
550
550
|
: []),
|
|
551
|
-
...(portal
|
|
551
|
+
...(portal || tenancy?.unknown === "404"
|
|
552
552
|
? [
|
|
553
|
-
" // Route guard: the declarative prefix→roles table from your config.",
|
|
554
|
-
" // An /api/* route always answers in JSON — redirecting fetch() to an",
|
|
555
|
-
" // HTML login page returns 200 and markup, which reads as success.",
|
|
556
553
|
" guard: (context) => {",
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
554
|
+
...(tenancy?.unknown === "404"
|
|
555
|
+
? [
|
|
556
|
+
" // A syntactically-valid tenant host whose label resolved to nothing is",
|
|
557
|
+
' // NOT a page (config `tenancy.unknown: "404"`). Falling through would',
|
|
558
|
+
" // render the marketing homepage on a stranger's subdomain. Reserved",
|
|
559
|
+
" // labels, app labels, and the apex never reach here — tenantLabel",
|
|
560
|
+
" // returns null for all of them, so they are not tenant candidates.",
|
|
561
|
+
" if (tenantLabel(context.url.hostname, TENANCY) && !context.locals.tenant) {",
|
|
562
|
+
' return new Response("Not found", { status: 404 });',
|
|
563
|
+
" }",
|
|
564
|
+
]
|
|
565
|
+
: []),
|
|
566
|
+
...(portal
|
|
567
|
+
? [
|
|
568
|
+
" // Route guard: the declarative prefix→roles table from your config.",
|
|
569
|
+
" // An /api/* route always answers in JSON — redirecting fetch() to an",
|
|
570
|
+
" // HTML login page returns 200 and markup, which reads as success.",
|
|
571
|
+
" const decision = portalGuard(",
|
|
572
|
+
" context.url.pathname,",
|
|
573
|
+
" context.locals.portalUser,",
|
|
574
|
+
" PORTAL_GUARD,",
|
|
575
|
+
" );",
|
|
576
|
+
" if (!decision) return undefined;",
|
|
577
|
+
' if (decision.kind === "redirect") return context.redirect(decision.location);',
|
|
578
|
+
" return guardResponse(decision) ?? undefined;",
|
|
579
|
+
]
|
|
580
|
+
: [" return undefined;"]),
|
|
565
581
|
" },",
|
|
566
582
|
]
|
|
567
583
|
: []),
|
|
@@ -573,9 +589,17 @@ export function generateAstroidMiddleware(config) {
|
|
|
573
589
|
" // redirect — so links built from Astro.url stay correct.",
|
|
574
590
|
" //",
|
|
575
591
|
" // An unknown subdomain is YOUR decision: `resolveTenant` returning null",
|
|
576
|
-
" // falls through to the ordinary site below
|
|
577
|
-
|
|
592
|
+
" // falls through to the ordinary site below — or 404s first, when the",
|
|
593
|
+
' // config sets `tenancy.unknown: "404"` (see the guard above).',
|
|
578
594
|
" rewrite: (context) => {",
|
|
595
|
+
" // Host-agnostic paths render from their own address. An API route is",
|
|
596
|
+
" // addressed absolutely by whatever calls it and reads the host from",
|
|
597
|
+
" // `locals.tenant`, so rewriting it moves it where no route matches —",
|
|
598
|
+
" // and on an app host with a catch-all page, the PAGE answers, so",
|
|
599
|
+
" // fetch() gets HTML instead of JSON and every data load quietly fails.",
|
|
600
|
+
` if (isRewriteExcluded(context.url.pathname, ${JSON.stringify(rewriteExclude)})) {`,
|
|
601
|
+
" return undefined;",
|
|
602
|
+
" }",
|
|
579
603
|
...(Object.keys(tenancy.apps ?? {}).length
|
|
580
604
|
? [
|
|
581
605
|
" // First-party app hosts (config `tenancy.apps`): a static label→prefix",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astroidjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Astroid — an opinionated meta-framework over Louise Toolkit and Astro for building editable, multi-editor sites on Cloudflare Workers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"louise-toolkit": "0.25.
|
|
63
|
+
"louise-toolkit": "0.25.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^24.13.3",
|