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/email/templates.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// inquiry pair (notify the owner, confirm to the sender).
|
|
5
5
|
//
|
|
6
6
|
// All three consuming sites wrote these four, with the same structure and
|
|
7
|
-
// near-identical copy
|
|
7
|
+
// near-identical copy—only the brand name differed, which is exactly what
|
|
8
8
|
// makes them first-party rather than site-side. The brand-agnostic *frame*
|
|
9
9
|
// (card, colour band, CTA button, paste-this-link fallback) already lives in
|
|
10
10
|
// `louise-toolkit/email`; this file owns the wording and the layout inside it.
|
|
@@ -24,7 +24,7 @@ const label = (theme, text, margin = "0 0 10px") => `<p style="font-family:${the
|
|
|
24
24
|
/** A quoted block for user-authored text (a message body). */
|
|
25
25
|
const quote = (theme, text) => `<div style="font-family:${theme.fonts.sans};font-size:15px;line-height:1.65;color:${theme.palette.ink};padding:16px 18px;background:${theme.palette.bgSoft};border:1px solid ${theme.palette.rule};border-radius:6px;">${escapeMultiline(text)}</div>`;
|
|
26
26
|
/**
|
|
27
|
-
* A one-time link email
|
|
27
|
+
* A one-time link email—the shared shape behind sign-in and password reset.
|
|
28
28
|
* Both are "here is a URL, it expires, ignore this if it wasn't you", and the
|
|
29
29
|
* only differences are the words.
|
|
30
30
|
*/
|
|
@@ -146,7 +146,7 @@ ${i.regarding?.trim() ? row("Regarding", escapeHtml(i.regarding.trim())) : ""}
|
|
|
146
146
|
/** Confirmation back to whoever submitted the contact form. */
|
|
147
147
|
export function inquiryConfirmationEmail(theme, i) {
|
|
148
148
|
const brand = theme.brand.name;
|
|
149
|
-
// Only the given name
|
|
149
|
+
// Only the given name—"Hi Jane Smith" reads like a form letter, which is
|
|
150
150
|
// precisely what this is trying not to.
|
|
151
151
|
const first = i.name.trim().split(/\s+/)[0] || "there";
|
|
152
152
|
const bodyHtml = [
|
package/dist/email/theme.d.ts
CHANGED
|
@@ -19,6 +19,6 @@ export interface MailThemeOverrides {
|
|
|
19
19
|
* ```
|
|
20
20
|
*
|
|
21
21
|
* An invalid or missing brand colour falls back to the ink neutral rather than
|
|
22
|
-
* throwing
|
|
22
|
+
* throwing—a malformed hex in settings should not take out password reset.
|
|
23
23
|
*/
|
|
24
24
|
export declare function astroidMailTheme(config: AstroidConfig, overrides?: MailThemeOverrides): MailTheme;
|
package/dist/email/theme.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Deriving a `MailTheme` from the project's brand.
|
|
4
4
|
//
|
|
5
|
-
// The toolkit's email shell takes a fully-specified theme
|
|
5
|
+
// The toolkit's email shell takes a fully-specified theme—ten palette slots, a
|
|
6
6
|
// colour band, three font stacks. Every site hand-picked all of it, which is
|
|
7
7
|
// exactly the kind of work a config should absorb: an Astroid project already
|
|
8
8
|
// declares `theme.colors`, and that is enough to produce a mail theme that looks
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// Two decisions here are load-bearing:
|
|
12
12
|
//
|
|
13
13
|
// 1. **Neutrals are fixed, brand colours are derived.** Page background, ink,
|
|
14
|
-
// rules
|
|
14
|
+
// rules—those are typography choices, not brand ones, and a site that
|
|
15
15
|
// wants different ones passes an override. What varies per brand is the
|
|
16
16
|
// accent and the colour band, and both come from `theme.colors`.
|
|
17
17
|
// 2. **The accent is contrast-corrected.** A pale brand colour used verbatim
|
|
@@ -60,7 +60,7 @@ function contrast(a, b) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Darken `color` until it clears `minRatio` against `bg`. A brand colour is
|
|
62
62
|
* chosen to look good on a website, and plenty of good ones (yellows, pale
|
|
63
|
-
* teals) are illegible as 11px uppercase text on a near-white email card
|
|
63
|
+
* teals) are illegible as 11px uppercase text on a near-white email card—mail
|
|
64
64
|
* clients offer no dark-mode escape hatch, so this is corrected up front.
|
|
65
65
|
*/
|
|
66
66
|
function readableOn(color, bg, minRatio = 4.5) {
|
|
@@ -118,7 +118,7 @@ function buildFonts(font) {
|
|
|
118
118
|
* ```
|
|
119
119
|
*
|
|
120
120
|
* An invalid or missing brand colour falls back to the ink neutral rather than
|
|
121
|
-
* throwing
|
|
121
|
+
* throwing—a malformed hex in settings should not take out password reset.
|
|
122
122
|
*/
|
|
123
123
|
export function astroidMailTheme(config, overrides = {}) {
|
|
124
124
|
const cardBg = hexToRgb(NEUTRALS.bg) ?? WHITE;
|
package/dist/errors.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare class AstroidConfigError extends Error {
|
|
|
10
10
|
* Distinct from {@link AstroidConfigError}, which is a build-time contract: this
|
|
11
11
|
* one fires on a live request, so it must be something a handler can catch and
|
|
12
12
|
* turn into a 5xx rather than something that reads like a misconfigured project.
|
|
13
|
-
* Reserved for cases where carrying on would be worse than failing
|
|
13
|
+
* Reserved for cases where carrying on would be worse than failing—a checkout
|
|
14
14
|
* whose idempotency key collides with another customer's, say, where the damage
|
|
15
15
|
* (a buyer who is never charged) is invisible at the call site.
|
|
16
16
|
*/
|
package/dist/errors.js
CHANGED
|
@@ -18,7 +18,7 @@ export class AstroidConfigError extends Error {
|
|
|
18
18
|
* Distinct from {@link AstroidConfigError}, which is a build-time contract: this
|
|
19
19
|
* one fires on a live request, so it must be something a handler can catch and
|
|
20
20
|
* turn into a 5xx rather than something that reads like a misconfigured project.
|
|
21
|
-
* Reserved for cases where carrying on would be worse than failing
|
|
21
|
+
* Reserved for cases where carrying on would be worse than failing—a checkout
|
|
22
22
|
* whose idempotency key collides with another customer's, say, where the damage
|
|
23
23
|
* (a buyer who is never charged) is invisible at the call site.
|
|
24
24
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// astroidjs
|
|
3
|
+
// astroidjs—the opinionated meta-framework over Louise Toolkit + Astro.
|
|
4
4
|
// Public entry. The configuration surface (`defineAstroid`) is the first
|
|
5
5
|
// inhabitant; the generator, theme system, and section library follow.
|
|
6
6
|
export * from "./analytics/index.js";
|
package/dist/map/pmtiles.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type RangeSpec = {
|
|
|
14
14
|
/**
|
|
15
15
|
* How the archive is read. A function rather than a bucket interface, and
|
|
16
16
|
* deliberately so: `R2Bucket.get` is overloaded, and its first overload
|
|
17
|
-
* *requires* an options argument
|
|
17
|
+
* *requires* an options argument—which means no structural interface with an
|
|
18
18
|
* optional second parameter can accept a real `R2Bucket`. Taking a reader lets
|
|
19
19
|
* the call site use R2's own types and resolves the mismatch at the source, and
|
|
20
20
|
* incidentally makes this work over any storage rather than only R2.
|
|
@@ -29,7 +29,7 @@ export interface RangeObject {
|
|
|
29
29
|
body?: ReadableStream | null;
|
|
30
30
|
/** Size of the WHOLE object, not the returned slice. */
|
|
31
31
|
size: number;
|
|
32
|
-
/** What R2 actually returned
|
|
32
|
+
/** What R2 actually returned—it clamps a range that runs past the end. */
|
|
33
33
|
range?: {
|
|
34
34
|
offset?: number;
|
|
35
35
|
length?: number;
|
|
@@ -58,7 +58,7 @@ export type ParsedRange = {
|
|
|
58
58
|
* Handles the three forms that matter:
|
|
59
59
|
* `bytes=0-1023` a bounded window
|
|
60
60
|
* `bytes=1024-` open-ended, to the end
|
|
61
|
-
* `bytes=-20000` the LAST n bytes
|
|
61
|
+
* `bytes=-20000` the LAST n bytes—the one the reference dropped
|
|
62
62
|
*
|
|
63
63
|
* Multi-range (`bytes=0-99,200-299`) returns null: it requires a multipart
|
|
64
64
|
* response no PMTiles client asks for, and serving the whole object is the
|
|
@@ -69,8 +69,8 @@ export interface PmtilesHandlerOptions {
|
|
|
69
69
|
/** Reads the archive, whole or by range. See {@link RangeReader}. */
|
|
70
70
|
read: RangeReader;
|
|
71
71
|
/**
|
|
72
|
-
* `Cache-Control` for the response. An archive is immutable
|
|
73
|
-
* overwrites the object wholesale
|
|
72
|
+
* `Cache-Control` for the response. An archive is immutable—a re-clip
|
|
73
|
+
* overwrites the object wholesale—so the byte ranges cache hard at the
|
|
74
74
|
* edge. Default one day.
|
|
75
75
|
*/
|
|
76
76
|
cacheControl?: string;
|
package/dist/map/pmtiles.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// Byte-range serving from R2
|
|
3
|
+
// Byte-range serving from R2—the plumbing under the self-hosted basemap.
|
|
4
4
|
//
|
|
5
5
|
// A PMTiles archive is one immutable blob, often hundreds of megabytes, and the
|
|
6
6
|
// client reads a few kilobytes at a time: a header, then directory pages, then
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
//
|
|
14
14
|
// The range parsing is deliberately complete. The implementation this
|
|
15
15
|
// generalizes matched only `bytes=<start>-<end?>`, so a SUFFIX range
|
|
16
|
-
// (`bytes=-20000`, "the last 20 KB"
|
|
16
|
+
// (`bytes=-20000`, "the last 20 KB"—how a client reads a footer without
|
|
17
17
|
// knowing the length) fell through to serving the ENTIRE archive. That is a
|
|
18
18
|
// correct-looking response and a catastrophic one.
|
|
19
19
|
/**
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Handles the three forms that matter:
|
|
23
23
|
* `bytes=0-1023` a bounded window
|
|
24
24
|
* `bytes=1024-` open-ended, to the end
|
|
25
|
-
* `bytes=-20000` the LAST n bytes
|
|
25
|
+
* `bytes=-20000` the LAST n bytes—the one the reference dropped
|
|
26
26
|
*
|
|
27
27
|
* Multi-range (`bytes=0-99,200-299`) returns null: it requires a multipart
|
|
28
28
|
* response no PMTiles client asks for, and serving the whole object is the
|
|
@@ -57,7 +57,7 @@ export function parseRangeHeader(header, size) {
|
|
|
57
57
|
const end = Number(bounded[2]);
|
|
58
58
|
if (end < start)
|
|
59
59
|
return { kind: "unsatisfiable" };
|
|
60
|
-
// An end past the object is clamped, not an error
|
|
60
|
+
// An end past the object is clamped, not an error—a client asking for more
|
|
61
61
|
// than exists gets what exists.
|
|
62
62
|
return { kind: "range", offset: start, length: Math.min(end, size - 1) - start + 1 };
|
|
63
63
|
}
|
|
@@ -107,7 +107,7 @@ export async function servePmtiles(request, options) {
|
|
|
107
107
|
: { offset: parsed.offset, ...(parsed.length ? { length: parsed.length } : {}) });
|
|
108
108
|
if (!object)
|
|
109
109
|
return new Response("Basemap not found", { status: 404 });
|
|
110
|
-
// Trust what R2 says it returned rather than what was asked for
|
|
110
|
+
// Trust what R2 says it returned rather than what was asked for—it clamps
|
|
111
111
|
// ranges, and a Content-Range that disagrees with the body corrupts the
|
|
112
112
|
// client's view of the archive.
|
|
113
113
|
const got = object.range ?? {};
|
package/dist/map/scaffold.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const ASTROID_PMTILES_PATH = "/map/basemap.pmtiles";
|
|
|
6
6
|
/** True when this project switched the map module on. */
|
|
7
7
|
export declare const usesMap: (config: AstroidConfig) => boolean;
|
|
8
8
|
/**
|
|
9
|
-
* `src/pages/map/basemap.pmtiles.ts
|
|
9
|
+
* `src/pages/map/basemap.pmtiles.ts`—the range-serving tile route.
|
|
10
10
|
*
|
|
11
11
|
* Thin: `servePmtiles` owns range parsing, the 206/416 contract, and trusting
|
|
12
12
|
* R2's clamped range over the requested one. What's here is which bucket and
|
|
@@ -14,7 +14,7 @@ export declare const usesMap: (config: AstroidConfig) => boolean;
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function generateMapTileRoute(config: AstroidConfig): string | null;
|
|
16
16
|
/**
|
|
17
|
-
* `src/components/MapEmbed.astro
|
|
17
|
+
* `src/components/MapEmbed.astro`—the map itself.
|
|
18
18
|
*
|
|
19
19
|
* The lazy load is not an optimisation detail, it's the reason this is usable:
|
|
20
20
|
* MapLibre is ~1 MB, and a location map is almost always below the fold. The
|
package/dist/map/scaffold.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// GENERATED rather than shipped as a component, for a concrete reason. MapLibre
|
|
6
6
|
// GL is ~1 MB and `pmtiles` is its companion; a `MapEmbed.astro` living in
|
|
7
7
|
// astroid's own `src/components/` would make both a hard requirement of the
|
|
8
|
-
// package
|
|
9
|
-
// the component library needing them too
|
|
8
|
+
// package—every project installing them, and the CI probe that type-checks
|
|
9
|
+
// the component library needing them too—for a feature most sites never turn
|
|
10
10
|
// on. Generating the component into the projects that enable the module keeps
|
|
11
11
|
// the dependency where the decision was made.
|
|
12
12
|
//
|
|
@@ -19,7 +19,7 @@ export const ASTROID_PMTILES_PATH = "/map/basemap.pmtiles";
|
|
|
19
19
|
/** True when this project switched the map module on. */
|
|
20
20
|
export const usesMap = (config) => (config.modules ?? []).includes("map");
|
|
21
21
|
/**
|
|
22
|
-
* `src/pages/map/basemap.pmtiles.ts
|
|
22
|
+
* `src/pages/map/basemap.pmtiles.ts`—the range-serving tile route.
|
|
23
23
|
*
|
|
24
24
|
* Thin: `servePmtiles` owns range parsing, the 206/416 contract, and trusting
|
|
25
25
|
* R2's clamped range over the requested one. What's here is which bucket and
|
|
@@ -68,7 +68,7 @@ export function generateMapTileRoute(config) {
|
|
|
68
68
|
].join("\n");
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* `src/components/MapEmbed.astro
|
|
71
|
+
* `src/components/MapEmbed.astro`—the map itself.
|
|
72
72
|
*
|
|
73
73
|
* The lazy load is not an optimisation detail, it's the reason this is usable:
|
|
74
74
|
* MapLibre is ~1 MB, and a location map is almost always below the fold. The
|
package/dist/map/style.d.ts
CHANGED
|
@@ -27,18 +27,18 @@ export interface MapStyleOptions {
|
|
|
27
27
|
pmtilesUrl: string;
|
|
28
28
|
colors?: MapColors;
|
|
29
29
|
/**
|
|
30
|
-
* SDF glyph URL template (
|
|
31
|
-
* Omit for an unlabelled map
|
|
30
|
+
* SDF glyph URL template (for example, `"/map/fonts/{fontstack}/{range}.pbf"`).
|
|
31
|
+
* Omit for an unlabelled map—which is the honest default, since labels
|
|
32
32
|
* without self-hosted glyphs mean an external font host and a looser CSP.
|
|
33
33
|
*/
|
|
34
34
|
glyphs?: string;
|
|
35
35
|
/** Font stack for labels. Only used when `glyphs` is set. */
|
|
36
36
|
fontstack?: string;
|
|
37
37
|
/** Attribution shown in the corner. Protomaps basemaps derive from OSM, and
|
|
38
|
-
* the licence requires the credit
|
|
38
|
+
* the licence requires the credit—so it defaults to present, not absent. */
|
|
39
39
|
attribution?: string;
|
|
40
40
|
}
|
|
41
|
-
/** A MapLibre style. Typed loosely on purpose
|
|
41
|
+
/** A MapLibre style. Typed loosely on purpose—see the header. */
|
|
42
42
|
export interface MapStyle {
|
|
43
43
|
version: 8;
|
|
44
44
|
glyphs?: string;
|
package/dist/map/style.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// rather than importing `maplibre-gl` (a megabyte) or `protomaps-themes-base`
|
|
7
7
|
// for its types. astroidjs stays installable by a project that will never draw
|
|
8
8
|
// a map, and a project that wants Protomaps' full maintained theme can swap
|
|
9
|
-
// this out
|
|
9
|
+
// this out—the only contract is "an object MapLibre accepts".
|
|
10
10
|
//
|
|
11
11
|
// The layer set is the quiet-basemap subset: land, water, green space, a road
|
|
12
12
|
// ramp with casings, buildings, and admin boundaries. Labels are opt-in and
|
package/dist/portal/config.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { PortalGuardConfig, PortalRoute } from "./guard.js";
|
|
|
3
3
|
/** Cookie prefix for the portal instance. Distinct from the studio's default
|
|
4
4
|
* (`better-auth`) so the two sessions can coexist on one origin. */
|
|
5
5
|
export declare const ASTROID_PORTAL_COOKIE_PREFIX = "portal";
|
|
6
|
-
/** Table-name prefix for the portal's Better Auth tables
|
|
6
|
+
/** Table-name prefix for the portal's Better Auth tables—`portal_user`,
|
|
7
7
|
* `portal_session`, … The studio owns the unprefixed names. */
|
|
8
8
|
export declare const ASTROID_PORTAL_TABLE_PREFIX = "portal_";
|
|
9
9
|
/** Everything the generated portal wiring needs, defaults applied. */
|
|
@@ -13,7 +13,7 @@ export interface ResolvedPortal {
|
|
|
13
13
|
cookiePrefix: string;
|
|
14
14
|
tablePrefix: string;
|
|
15
15
|
roles: string[];
|
|
16
|
-
/** First role in `roles
|
|
16
|
+
/** First role in `roles`—what a newly created account gets. */
|
|
17
17
|
defaultRole: string;
|
|
18
18
|
routes: PortalRoute[];
|
|
19
19
|
home: Record<string, string>;
|
package/dist/portal/config.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// Portal defaults derived from the project config
|
|
3
|
+
// Portal defaults derived from the project config—the single place that knows
|
|
4
4
|
// the portal's mount, cookie prefix, table prefix, and guard table.
|
|
5
5
|
//
|
|
6
6
|
// The isolation constants are fixed rather than configurable, and that's the
|
|
7
7
|
// point: the studio instance MUST keep Better Auth's defaults (`/api/auth`, the
|
|
8
8
|
// unprefixed tables) because the Louise editor client hardcodes them, so the
|
|
9
9
|
// portal is the one that moves. Leaving that to a project invites the one
|
|
10
|
-
// mistake that matters
|
|
10
|
+
// mistake that matters—two instances sharing a cookie prefix, where signing
|
|
11
11
|
// into one silently signs you out of the other, intermittently, in production.
|
|
12
12
|
import { ASTROID_PORTAL_BASE_PATH } from "../security/rate-rules.js";
|
|
13
13
|
/** Cookie prefix for the portal instance. Distinct from the studio's default
|
|
14
14
|
* (`better-auth`) so the two sessions can coexist on one origin. */
|
|
15
15
|
export const ASTROID_PORTAL_COOKIE_PREFIX = "portal";
|
|
16
|
-
/** Table-name prefix for the portal's Better Auth tables
|
|
16
|
+
/** Table-name prefix for the portal's Better Auth tables—`portal_user`,
|
|
17
17
|
* `portal_session`, … The studio owns the unprefixed names. */
|
|
18
18
|
export const ASTROID_PORTAL_TABLE_PREFIX = "portal_";
|
|
19
19
|
/** Default guard table: the account area, for any signed-in portal user. */
|
package/dist/portal/guard.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface PortalUser {
|
|
|
11
11
|
}
|
|
12
12
|
/** One rule: everything under `prefix` requires one of `roles`. */
|
|
13
13
|
export interface PortalRoute {
|
|
14
|
-
/** Path prefix,
|
|
14
|
+
/** Path prefix, for example, `/portal`—matches the prefix itself and everything
|
|
15
15
|
* beneath it, but NOT `/portalling`. */
|
|
16
16
|
prefix: string;
|
|
17
17
|
/** Roles allowed through. Empty means "any signed-in user". */
|
|
@@ -22,7 +22,7 @@ export interface PortalGuardConfig {
|
|
|
22
22
|
routes: PortalRoute[];
|
|
23
23
|
/** Where to send a signed-out visitor. Default `/login`. */
|
|
24
24
|
loginPath?: string;
|
|
25
|
-
/** Landing page for a signed-in user, by role
|
|
25
|
+
/** Landing page for a signed-in user, by role—used to bounce someone who
|
|
26
26
|
* reached an area they don't belong in. Default `/portal` for everyone. */
|
|
27
27
|
home?: (role: string) => string;
|
|
28
28
|
}
|
|
@@ -38,13 +38,13 @@ export type GuardDecision = null | {
|
|
|
38
38
|
error: string;
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
|
-
/** Prefix match on a path SEGMENT boundary
|
|
41
|
+
/** Prefix match on a path SEGMENT boundary—`/portal` covers `/portal` and
|
|
42
42
|
* `/portal/orders`, but never `/portalling`. */
|
|
43
43
|
export declare function matchesPrefix(path: string, prefix: string): boolean;
|
|
44
44
|
/**
|
|
45
45
|
* Decide whether a request may proceed.
|
|
46
46
|
*
|
|
47
|
-
* Pure
|
|
47
|
+
* Pure—it returns a decision rather than a `Response`, so it's testable
|
|
48
48
|
* without an Astro context and the middleware stays responsible for turning a
|
|
49
49
|
* decision into a redirect or a body.
|
|
50
50
|
*/
|
package/dist/portal/guard.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// coracle and ghostfire independently built the same thing: a declarative table
|
|
6
6
|
// of `prefix → roles`, walked once per request. Declarative rather than a guard
|
|
7
7
|
// call inside each page, because a guard you have to remember to write is a
|
|
8
|
-
// guard someone eventually forgets
|
|
8
|
+
// guard someone eventually forgets—and the page that forgets it is the one
|
|
9
9
|
// that leaks.
|
|
10
10
|
//
|
|
11
11
|
// Three answers, and which one you give matters:
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
// not signed in, API → 401 JSON (a redirect to an HTML login page is
|
|
15
15
|
// useless to fetch(); it looks like success)
|
|
16
16
|
// signed in, wrong role → 403 for API, and for HTML a redirect to the area
|
|
17
|
-
// this user DOES have
|
|
17
|
+
// this user DOES have—not back to login, which
|
|
18
18
|
// reads as "your password failed" when it didn't
|
|
19
|
-
/** Prefix match on a path SEGMENT boundary
|
|
19
|
+
/** Prefix match on a path SEGMENT boundary—`/portal` covers `/portal` and
|
|
20
20
|
* `/portal/orders`, but never `/portalling`. */
|
|
21
21
|
export function matchesPrefix(path, prefix) {
|
|
22
22
|
return path === prefix || path.startsWith(`${prefix}/`);
|
|
@@ -24,7 +24,7 @@ export function matchesPrefix(path, prefix) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Decide whether a request may proceed.
|
|
26
26
|
*
|
|
27
|
-
* Pure
|
|
27
|
+
* Pure—it returns a decision rather than a `Response`, so it's testable
|
|
28
28
|
* without an Astro context and the middleware stays responsible for turning a
|
|
29
29
|
* decision into a redirect or a body.
|
|
30
30
|
*/
|
package/dist/portal/nav.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// The portal's navigation, as data.
|
|
4
4
|
//
|
|
5
5
|
// Two things fall out of declaring it rather than writing markup per page.
|
|
6
|
-
// Items can be filtered by the viewer's role in one place
|
|
6
|
+
// Items can be filtered by the viewer's role in one place—so an item a user
|
|
7
7
|
// can't reach is never rendered, instead of rendered-then-403'd, which reads as
|
|
8
8
|
// a broken link. And "which item is active" is computed the same way the guard
|
|
9
9
|
// matches prefixes, so the highlight can't disagree with the routing.
|
|
@@ -26,7 +26,7 @@ export function definePortalNav(items) {
|
|
|
26
26
|
},
|
|
27
27
|
activeFor(path) {
|
|
28
28
|
// Longest href first, so `/portal/orders` wins over `/portal` on a page
|
|
29
|
-
// both would match
|
|
29
|
+
// both would match—otherwise the parent item is always the active one.
|
|
30
30
|
return ([...items]
|
|
31
31
|
.sort((a, b) => b.href.length - a.href.length)
|
|
32
32
|
.find((item) => matchesPrefix(path, item.href)) ?? null);
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { AstroidConfig } from "../config.js";
|
|
2
2
|
/**
|
|
3
|
-
* `src/portal-auth.ts
|
|
3
|
+
* `src/portal-auth.ts`—the portal Better Auth instance and its session
|
|
4
4
|
* resolver.
|
|
5
5
|
*
|
|
6
6
|
* Returns null when the project has no portal.
|
|
7
7
|
*/
|
|
8
8
|
export declare function generateAstroidPortalAuth(config: AstroidConfig): string | null;
|
|
9
9
|
/**
|
|
10
|
-
* `src/pages/api/portal-auth/[...all].ts
|
|
10
|
+
* `src/pages/api/portal-auth/[...all].ts`—the portal's Better Auth catch-all,
|
|
11
11
|
* mounted at its own basePath so it never collides with the studio's
|
|
12
12
|
* `/api/auth`.
|
|
13
13
|
*
|
|
14
14
|
* Lives here rather than as a literal in `create-astroid` for the same reason
|
|
15
15
|
* the archetype sections moved (#277): the scaffolder is plain JS, so a drifted
|
|
16
16
|
* import path there is invisible until a user's build fails. It is also the half
|
|
17
|
-
* `generateAstroidPortalAuth` is useless without
|
|
17
|
+
* `generateAstroidPortalAuth` is useless without—`src/portal-auth.ts` exports
|
|
18
18
|
* `handlePortalAuth`, and nothing calls it unless this route exists.
|
|
19
19
|
*
|
|
20
20
|
* Returns null when the project has no portal.
|
|
@@ -22,7 +22,7 @@ export declare function generateAstroidPortalAuth(config: AstroidConfig): string
|
|
|
22
22
|
export declare function generateAstroidPortalAuthRoute(config: AstroidConfig): string | null;
|
|
23
23
|
/**
|
|
24
24
|
* The `App.Locals` member the portal adds, as a block `create-astroid`
|
|
25
|
-
* substitutes into `src/env.d.ts`. Empty without a portal
|
|
25
|
+
* substitutes into `src/env.d.ts`. Empty without a portal—a project that
|
|
26
26
|
* types `portalUser` it never sets is inviting a null-check nobody needs.
|
|
27
27
|
*/
|
|
28
28
|
export declare function generateAstroidPortalLocals(config: AstroidConfig): string;
|
package/dist/portal/scaffold.js
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
// The portal's SCAFFOLD-ONCE pieces: the second Better Auth instance, and the
|
|
4
4
|
// `App.Locals` / `CloudflareEnv` additions that come with it.
|
|
5
5
|
//
|
|
6
|
-
// The auth instance is scaffolded rather than generated because a site edits it
|
|
7
|
-
//
|
|
6
|
+
// The auth instance is scaffolded rather than generated because a site edits it—the
|
|
7
|
+
// reset email, the role a new account gets, extra user columns. What
|
|
8
8
|
// Astroid fixes are the three things that must not drift: the mount, the cookie
|
|
9
9
|
// prefix, and the table prefix. Get any of those wrong and the two instances
|
|
10
10
|
// fight over one origin's cookies, which fails intermittently and looks like a
|
|
11
11
|
// session bug rather than a configuration one.
|
|
12
12
|
import { astroidPortal } from "./config.js";
|
|
13
13
|
/**
|
|
14
|
-
* `src/portal-auth.ts
|
|
14
|
+
* `src/portal-auth.ts`—the portal Better Auth instance and its session
|
|
15
15
|
* resolver.
|
|
16
16
|
*
|
|
17
17
|
* Returns null when the project has no portal.
|
|
@@ -97,14 +97,14 @@ export function generateAstroidPortalAuth(config) {
|
|
|
97
97
|
].join("\n");
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
100
|
-
* `src/pages/api/portal-auth/[...all].ts
|
|
100
|
+
* `src/pages/api/portal-auth/[...all].ts`—the portal's Better Auth catch-all,
|
|
101
101
|
* mounted at its own basePath so it never collides with the studio's
|
|
102
102
|
* `/api/auth`.
|
|
103
103
|
*
|
|
104
104
|
* Lives here rather than as a literal in `create-astroid` for the same reason
|
|
105
105
|
* the archetype sections moved (#277): the scaffolder is plain JS, so a drifted
|
|
106
106
|
* import path there is invisible until a user's build fails. It is also the half
|
|
107
|
-
* `generateAstroidPortalAuth` is useless without
|
|
107
|
+
* `generateAstroidPortalAuth` is useless without—`src/portal-auth.ts` exports
|
|
108
108
|
* `handlePortalAuth`, and nothing calls it unless this route exists.
|
|
109
109
|
*
|
|
110
110
|
* Returns null when the project has no portal.
|
|
@@ -126,7 +126,7 @@ export function generateAstroidPortalAuthRoute(config) {
|
|
|
126
126
|
}
|
|
127
127
|
/**
|
|
128
128
|
* The `App.Locals` member the portal adds, as a block `create-astroid`
|
|
129
|
-
* substitutes into `src/env.d.ts`. Empty without a portal
|
|
129
|
+
* substitutes into `src/env.d.ts`. Empty without a portal—a project that
|
|
130
130
|
* types `portalUser` it never sets is inviting a null-check nobody needs.
|
|
131
131
|
*/
|
|
132
132
|
export function generateAstroidPortalLocals(config) {
|
package/dist/portal/session.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type PortalSessionResolver = (request: Request) => Promise<PortalUser | n
|
|
|
8
8
|
* request both await one lookup rather than starting a second.
|
|
9
9
|
*/
|
|
10
10
|
export declare function resolvePortalSession(request: Request, resolve: PortalSessionResolver): Promise<PortalUser | null>;
|
|
11
|
-
/** JSON response helper
|
|
11
|
+
/** JSON response helper—the shape every portal API route returns. */
|
|
12
12
|
export declare function json(body: unknown, status?: number, headers?: Record<string, string>): Response;
|
|
13
13
|
/** True when the request came from this same origin. */
|
|
14
14
|
export declare function isSameOrigin(request: Request): boolean;
|
|
@@ -20,7 +20,7 @@ export type CustomerGuardResult = {
|
|
|
20
20
|
response: Response;
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
|
-
* Guard a portal API handler: a signed-in user, and
|
|
23
|
+
* Guard a portal API handler: a signed-in user, and—on mutations—a
|
|
24
24
|
* same-origin request.
|
|
25
25
|
*
|
|
26
26
|
* ```ts
|
package/dist/portal/session.js
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
// The middleware resolves it (to gate routes) and so does whatever handler runs
|
|
6
6
|
// next (to know who's asking). Both hitting the session store is a wasted D1
|
|
7
7
|
// round-trip on every authenticated request, so the in-flight promise is shared
|
|
8
|
-
// per request via a `WeakMap
|
|
8
|
+
// per request via a `WeakMap`—keyed on the `Request`, which means entries
|
|
9
9
|
// disappear with the request rather than needing eviction.
|
|
10
10
|
//
|
|
11
11
|
// `requireCustomer` then adds the check a session alone doesn't give you:
|
|
12
12
|
// same-origin on mutations. A cookie is attached by the browser to any request
|
|
13
|
-
// to this origin, including one a third-party page triggered
|
|
13
|
+
// to this origin, including one a third-party page triggered—so a session
|
|
14
14
|
// proves identity, and the origin check proves intent.
|
|
15
15
|
const inFlight = new WeakMap();
|
|
16
16
|
/**
|
|
@@ -29,7 +29,7 @@ export function resolvePortalSession(request, resolve) {
|
|
|
29
29
|
inFlight.set(request, promise);
|
|
30
30
|
return promise;
|
|
31
31
|
}
|
|
32
|
-
/** JSON response helper
|
|
32
|
+
/** JSON response helper—the shape every portal API route returns. */
|
|
33
33
|
export function json(body, status = 200, headers = {}) {
|
|
34
34
|
return new Response(JSON.stringify(body), {
|
|
35
35
|
status,
|
|
@@ -46,7 +46,7 @@ export function isSameOrigin(request) {
|
|
|
46
46
|
return origin === target;
|
|
47
47
|
// No Origin header: browsers always send one on cross-origin mutations, so
|
|
48
48
|
// its absence means a same-origin or non-browser caller. Fall back to Referer
|
|
49
|
-
// when present, and allow otherwise
|
|
49
|
+
// when present, and allow otherwise—being stricter would break legitimate
|
|
50
50
|
// server-to-server callers without stopping a real CSRF, which always carries
|
|
51
51
|
// an Origin.
|
|
52
52
|
const referer = request.headers.get("referer");
|
|
@@ -61,7 +61,7 @@ export function isSameOrigin(request) {
|
|
|
61
61
|
return true;
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
* Guard a portal API handler: a signed-in user, and
|
|
64
|
+
* Guard a portal API handler: a signed-in user, and—on mutations—a
|
|
65
65
|
* same-origin request.
|
|
66
66
|
*
|
|
67
67
|
* ```ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AstroidConfig } from "../config.js";
|
|
2
2
|
/**
|
|
3
|
-
* `src/pages/work.astro
|
|
3
|
+
* `src/pages/work.astro`—the portfolio gallery. Null for any other archetype.
|
|
4
4
|
*
|
|
5
5
|
* Intrinsic `width`/`height` are carried through deliberately: they feed the
|
|
6
6
|
* pre-decode layout, so a library with dimensions recorded lays out correctly on
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
// The `portfolio` archetype's scaffold-once page: a justified gallery over the
|
|
4
4
|
// media library.
|
|
5
5
|
//
|
|
6
|
-
// Scaffold-once, not regenerated, for the usual reason
|
|
6
|
+
// Scaffold-once, not regenerated, for the usual reason—this is the first file
|
|
7
7
|
// a portfolio site edits (which assets appear, in what order, whether tiles link
|
|
8
8
|
// to a detail page), so `astroid generate` must never rewrite it.
|
|
9
9
|
//
|
|
10
10
|
// It exists because the primitives alone don't finish the job. `<MediaSlot>` and
|
|
11
11
|
// `<JustifiedGallery>` are archetype-agnostic, but the wiring between them and
|
|
12
|
-
// the media registry
|
|
12
|
+
// the media registry—the public URL shape, filtering to images, carrying
|
|
13
13
|
// alt/caption and intrinsic dimensions through so the first paint isn't a guess
|
|
14
|
-
|
|
14
|
+
//—is identical every time, and is exactly what the consuming sites hand-wrote.
|
|
15
15
|
/**
|
|
16
|
-
* `src/pages/work.astro
|
|
16
|
+
* `src/pages/work.astro`—the portfolio gallery. Null for any other archetype.
|
|
17
17
|
*
|
|
18
18
|
* Intrinsic `width`/`height` are carried through deliberately: they feed the
|
|
19
19
|
* pre-decode layout, so a library with dimensions recorded lays out correctly on
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { AstroidConfig } from "../config.js";
|
|
2
|
-
/** `src/actions/index.ts
|
|
2
|
+
/** `src/actions/index.ts`—the typed mutation surface, scaffolded once. */
|
|
3
3
|
export declare function generateAstroidActions(config: AstroidConfig): string;
|
package/dist/project/actions.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
// Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
|
|
2
2
|
//
|
|
3
|
-
// `src/actions/index.ts
|
|
3
|
+
// `src/actions/index.ts`—the Astro-native, typed mutation surface (ADR 0001
|
|
4
4
|
// layer 2), beside the framework-agnostic `/api/louise/*` routes.
|
|
5
5
|
//
|
|
6
6
|
// Astroid generated only the route half. That is not a missing convenience: the
|
|
7
7
|
// two entrypoints write the SAME rows, and the whole reason `@louise-toolkit/astro`
|
|
8
|
-
// exposes these factories is that each one shares the raw route's store
|
|
9
|
-
//
|
|
8
|
+
// exposes these factories is that each one shares the raw route's store
|
|
9
|
+
// path—`applyFieldSave`, `applySettingsPatch`, `applySaveDraft`. A project that wired
|
|
10
10
|
// its own Actions by hand would get a second write path, and a second write path
|
|
11
11
|
// is where validation, sanitization, and draft-merge semantics drift apart
|
|
12
12
|
// silently (#138).
|
|
13
13
|
//
|
|
14
|
-
// So this file is SCAFFOLD-ONCE and is meant to be added to
|
|
15
|
-
// keeps its own bespoke actions right beside these
|
|
14
|
+
// So this file is SCAFFOLD-ONCE and is meant to be added to—the reference site
|
|
15
|
+
// keeps its own bespoke actions right beside these—but the three below come
|
|
16
16
|
// pre-wired against the same tables and the same collection config the generated
|
|
17
17
|
// worker uses.
|
|
18
|
-
/** `src/actions/index.ts
|
|
18
|
+
/** `src/actions/index.ts`—the typed mutation surface, scaffolded once. */
|
|
19
19
|
export function generateAstroidActions(config) {
|
|
20
20
|
const customKeys = config.settings?.customKeys ?? [];
|
|
21
21
|
const extraImageKeys = config.settings?.imageKeys ?? [];
|