astroidjs 0.7.1 → 0.7.2
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 +20 -0
- package/dist/config.js +18 -0
- package/dist/tenancy/index.d.ts +15 -3
- package/dist/tenancy/index.js +44 -13
- package/dist/worker/generate.js +12 -1
- package/package.json +2 -2
package/dist/config.d.ts
CHANGED
|
@@ -268,6 +268,26 @@ export interface TenancyConfig {
|
|
|
268
268
|
* built from `Astro.url` stay public and correct.
|
|
269
269
|
*/
|
|
270
270
|
rewritePrefix?: string;
|
|
271
|
+
/**
|
|
272
|
+
* First-party apps on their own labels, mapped to the internal path prefix
|
|
273
|
+
* each serves from — `{ studio: "/studio" }` serves
|
|
274
|
+
* `studio.example.com/<path>` from `src/pages/studio/<path>`.
|
|
275
|
+
*
|
|
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 — the studio exists
|
|
278
|
+
* whether or not any tenant does) and not merely reserved (a reserved label
|
|
279
|
+
* renders the ordinary site, which turns the admin host into a second copy
|
|
280
|
+
* of the marketing homepage). It is a static rewrite, decided at config time.
|
|
281
|
+
*
|
|
282
|
+
* An app label is implicitly reserved: `tenantLabel` never offers it to
|
|
283
|
+
* `resolveTenant`, and listing it in `reserved` too is refused — one list
|
|
284
|
+
* per fact, or the two drift.
|
|
285
|
+
*
|
|
286
|
+
* Same internal-rewrite semantics as tenants: the visitor's URL never
|
|
287
|
+
* changes, and the path form stays reachable on the apex — which is what
|
|
288
|
+
* makes local dev work, since `wrangler dev` cannot serve subdomains.
|
|
289
|
+
*/
|
|
290
|
+
apps?: Record<string, string>;
|
|
271
291
|
}
|
|
272
292
|
export interface AstroidCron {
|
|
273
293
|
/** Standard 5-field cron, UTC — e.g. `"*/15 * * * *"`. */
|
package/dist/config.js
CHANGED
|
@@ -141,6 +141,24 @@ function assertTenancy(config) {
|
|
|
141
141
|
"A wildcard route does not match its own apex, so the apex would 404. " +
|
|
142
142
|
`Add "${apex}" to \`hosts\`.`);
|
|
143
143
|
}
|
|
144
|
+
// App labels: each failure below otherwise surfaces as a rewrite to a path
|
|
145
|
+
// that renders the wrong page, with nothing pointing back at the config.
|
|
146
|
+
for (const [label, prefix] of Object.entries(tenancy.apps ?? {})) {
|
|
147
|
+
if (!label || label.includes(".")) {
|
|
148
|
+
throw new AstroidConfigError(`\`tenancy.apps\` label "${label}" must be a single subdomain label — ` +
|
|
149
|
+
"Cloudflare's wildcard matches one level.");
|
|
150
|
+
}
|
|
151
|
+
if ((tenancy.reserved ?? []).includes(label)) {
|
|
152
|
+
throw new AstroidConfigError(`"${label}" is in both \`tenancy.apps\` and \`tenancy.reserved\`. ` +
|
|
153
|
+
"An app label is implicitly reserved — keep it in `apps` only, or the " +
|
|
154
|
+
"two lists drift and `reserved` silently wins.");
|
|
155
|
+
}
|
|
156
|
+
if (!prefix.startsWith("/") || prefix === "/" || prefix.endsWith("/")) {
|
|
157
|
+
throw new AstroidConfigError(`\`tenancy.apps.${label}\` must be an internal path prefix like "/studio" ` +
|
|
158
|
+
`(leading slash, no trailing slash, not "/") — got "${prefix}". ` +
|
|
159
|
+
'The rewrite is `prefix + pathname`, so "/" or a trailing slash produces "//…".');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
144
162
|
}
|
|
145
163
|
export function defineAstroid(config) {
|
|
146
164
|
if (!config.key || config.key.trim().length === 0) {
|
package/dist/tenancy/index.d.ts
CHANGED
|
@@ -15,14 +15,26 @@ export declare function tenancyZone(tenancy: TenancyConfig): string;
|
|
|
15
15
|
* The subdomain label for a host under the wildcard, or `null` when the host is
|
|
16
16
|
* not a tenant candidate at all.
|
|
17
17
|
*
|
|
18
|
-
* `null` covers
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* `null` covers four distinct cases that all mean "not a tenant": the apex
|
|
19
|
+
* itself (a wildcard does not match its own apex), a host outside the pattern
|
|
20
|
+
* (a preview domain, `localhost`), a reserved label, and an app label — which
|
|
21
|
+
* has its own static rewrite via {@link appPrefix} instead of a lookup.
|
|
21
22
|
*
|
|
22
23
|
* Exported and pure so a site can unit-test its own reserved list without
|
|
23
24
|
* standing up a request.
|
|
24
25
|
*/
|
|
25
26
|
export declare function tenantLabel(host: string, tenancy: TenancyConfig): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* The internal path prefix an app host rewrites to, or `null` when the host is
|
|
29
|
+
* not an app host — `appPrefix("studio.example.com", …)` → `"/studio"` under
|
|
30
|
+
* `apps: { studio: "/studio" }`.
|
|
31
|
+
*
|
|
32
|
+
* Static by design: an app exists whether or not any tenant does, so there is
|
|
33
|
+
* no per-request lookup and nothing to cache. Same host handling as
|
|
34
|
+
* {@link tenantLabel} (port and case ignored, one label only), so `wrangler
|
|
35
|
+
* dev` behaves like production.
|
|
36
|
+
*/
|
|
37
|
+
export declare function appPrefix(host: string, tenancy: TenancyConfig): string | null;
|
|
26
38
|
/**
|
|
27
39
|
* The scaffold-once `src/tenancy.ts` — the seam holding every decision Astroid
|
|
28
40
|
* refuses to make for a site.
|
package/dist/tenancy/index.js
CHANGED
|
@@ -23,30 +23,61 @@ export function tenancyZone(tenancy) {
|
|
|
23
23
|
return tenancy.zone ?? tenancy.hostPattern.replace(/^\*\./, "");
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* The subdomain label
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* the apex itself (a wildcard does not match its own apex), a host outside the
|
|
31
|
-
* pattern (a preview domain, `localhost`), and a reserved label.
|
|
32
|
-
*
|
|
33
|
-
* Exported and pure so a site can unit-test its own reserved list without
|
|
34
|
-
* standing up a request.
|
|
26
|
+
* The single subdomain label under the wildcard, before any policy — or `null`
|
|
27
|
+
* for the apex, an off-pattern host, or a dotted label. Shared by
|
|
28
|
+
* {@link tenantLabel} and {@link appPrefix} so their host handling (port,
|
|
29
|
+
* case, one-level-only) cannot drift.
|
|
35
30
|
*/
|
|
36
|
-
|
|
31
|
+
function hostLabel(host, tenancy) {
|
|
37
32
|
const suffix = tenancy.hostPattern.replace(/^\*\./, "");
|
|
38
33
|
// Strip a port: `acme.example.com:8788` under `wrangler dev`.
|
|
39
34
|
const hostname = host.split(":")[0]?.toLowerCase() ?? "";
|
|
40
35
|
if (!hostname.endsWith(`.${suffix}`))
|
|
41
36
|
return null;
|
|
42
37
|
const label = hostname.slice(0, -(suffix.length + 1));
|
|
43
|
-
// Only a single label
|
|
38
|
+
// Only a single label counts. `a.b.example.com` under `*.example.com` is
|
|
44
39
|
// not `a.b` — Cloudflare's wildcard matches one level, and treating a dotted
|
|
45
40
|
// string as a slug would put a `/` in a rewrite path.
|
|
46
41
|
if (!label || label.includes("."))
|
|
47
42
|
return null;
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
return label;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The subdomain label for a host under the wildcard, or `null` when the host is
|
|
47
|
+
* not a tenant candidate at all.
|
|
48
|
+
*
|
|
49
|
+
* `null` covers four distinct cases that all mean "not a tenant": the apex
|
|
50
|
+
* itself (a wildcard does not match its own apex), a host outside the pattern
|
|
51
|
+
* (a preview domain, `localhost`), a reserved label, and an app label — which
|
|
52
|
+
* has its own static rewrite via {@link appPrefix} instead of a lookup.
|
|
53
|
+
*
|
|
54
|
+
* Exported and pure so a site can unit-test its own reserved list without
|
|
55
|
+
* standing up a request.
|
|
56
|
+
*/
|
|
57
|
+
export function tenantLabel(host, tenancy) {
|
|
58
|
+
const label = hostLabel(host, tenancy);
|
|
59
|
+
if (!label)
|
|
60
|
+
return null;
|
|
61
|
+
if ((tenancy.reserved ?? []).includes(label))
|
|
62
|
+
return null;
|
|
63
|
+
return tenancy.apps && label in tenancy.apps ? null : label;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The internal path prefix an app host rewrites to, or `null` when the host is
|
|
67
|
+
* not an app host — `appPrefix("studio.example.com", …)` → `"/studio"` under
|
|
68
|
+
* `apps: { studio: "/studio" }`.
|
|
69
|
+
*
|
|
70
|
+
* Static by design: an app exists whether or not any tenant does, so there is
|
|
71
|
+
* no per-request lookup and nothing to cache. Same host handling as
|
|
72
|
+
* {@link tenantLabel} (port and case ignored, one label only), so `wrangler
|
|
73
|
+
* dev` behaves like production.
|
|
74
|
+
*/
|
|
75
|
+
export function appPrefix(host, tenancy) {
|
|
76
|
+
const apps = tenancy.apps;
|
|
77
|
+
if (!apps)
|
|
78
|
+
return null;
|
|
79
|
+
const label = hostLabel(host, tenancy);
|
|
80
|
+
return label ? (apps[label] ?? null) : null;
|
|
50
81
|
}
|
|
51
82
|
/**
|
|
52
83
|
* The scaffold-once `src/tenancy.ts` — the seam holding every decision Astroid
|
package/dist/worker/generate.js
CHANGED
|
@@ -478,7 +478,9 @@ export function generateAstroidMiddleware(config) {
|
|
|
478
478
|
...(portal ? ["astroidPortalGuardConfig"] : []),
|
|
479
479
|
"astroidRateRules",
|
|
480
480
|
...(portal ? ["guardResponse", "portalGuard", "resolvePortalSession"] : []),
|
|
481
|
-
...(tenancy
|
|
481
|
+
...(tenancy
|
|
482
|
+
? [...(Object.keys(tenancy.apps ?? {}).length ? ["appPrefix"] : []), "tenantLabel"]
|
|
483
|
+
: []),
|
|
482
484
|
].join(", ")} } from "astroidjs";`,
|
|
483
485
|
'import astroidConfig from "../astroid.config.js";',
|
|
484
486
|
...(tenancy
|
|
@@ -574,6 +576,15 @@ export function generateAstroidMiddleware(config) {
|
|
|
574
576
|
" // falls through to the ordinary site below. Return a 404 from `guard`",
|
|
575
577
|
" // instead if a stranger's subdomain should not render your homepage.",
|
|
576
578
|
" rewrite: (context) => {",
|
|
579
|
+
...(Object.keys(tenancy.apps ?? {}).length
|
|
580
|
+
? [
|
|
581
|
+
" // First-party app hosts (config `tenancy.apps`): a static label→prefix",
|
|
582
|
+
" // map, checked before the tenant — an app exists whether or not any",
|
|
583
|
+
" // tenant does, and needs no lookup.",
|
|
584
|
+
" const app = appPrefix(context.url.hostname, TENANCY);",
|
|
585
|
+
" if (app) return `${app}${context.url.pathname}`;",
|
|
586
|
+
]
|
|
587
|
+
: []),
|
|
577
588
|
" const tenant = context.locals.tenant;",
|
|
578
589
|
` return tenant ? \`${rewritePrefix}/\${tenant.slug}\${context.url.pathname}\` : undefined;`,
|
|
579
590
|
" },",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astroidjs",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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.
|
|
63
|
+
"louise-toolkit": "0.25.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^24.13.3",
|