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.
Files changed (133) hide show
  1. package/README.md +42 -42
  2. package/bin/astroid.mjs +25 -25
  3. package/dist/analytics/index.d.ts +3 -3
  4. package/dist/analytics/index.js +9 -9
  5. package/dist/astro/csp.d.ts +5 -5
  6. package/dist/astro/csp.js +6 -6
  7. package/dist/astro/index.js +1 -1
  8. package/dist/auth/index.d.ts +2 -2
  9. package/dist/auth/index.js +5 -5
  10. package/dist/commerce/adapters.d.ts +6 -6
  11. package/dist/commerce/adapters.js +9 -9
  12. package/dist/commerce/checkout-scaffold.d.ts +5 -5
  13. package/dist/commerce/checkout-scaffold.js +9 -9
  14. package/dist/commerce/checkout.d.ts +12 -12
  15. package/dist/commerce/checkout.js +7 -7
  16. package/dist/commerce/loader.d.ts +2 -2
  17. package/dist/commerce/loader.js +3 -3
  18. package/dist/commerce/mirror.d.ts +4 -4
  19. package/dist/commerce/mirror.js +9 -9
  20. package/dist/commerce/roles.d.ts +10 -10
  21. package/dist/commerce/roles.js +13 -13
  22. package/dist/commerce/secrets.d.ts +9 -9
  23. package/dist/commerce/secrets.js +9 -9
  24. package/dist/commerce/sync.d.ts +7 -7
  25. package/dist/commerce/sync.js +5 -5
  26. package/dist/components/sections.d.ts +9 -9
  27. package/dist/components/sections.js +12 -12
  28. package/dist/config.d.ts +62 -62
  29. package/dist/config.js +18 -18
  30. package/dist/email/inquiry.d.ts +2 -2
  31. package/dist/email/inquiry.js +1 -1
  32. package/dist/email/send.d.ts +4 -4
  33. package/dist/email/send.js +7 -7
  34. package/dist/email/templates.js +3 -3
  35. package/dist/email/theme.d.ts +1 -1
  36. package/dist/email/theme.js +4 -4
  37. package/dist/errors.d.ts +1 -1
  38. package/dist/errors.js +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/map/pmtiles.d.ts +5 -5
  41. package/dist/map/pmtiles.js +5 -5
  42. package/dist/map/scaffold.d.ts +2 -2
  43. package/dist/map/scaffold.js +4 -4
  44. package/dist/map/style.d.ts +4 -4
  45. package/dist/map/style.js +1 -1
  46. package/dist/portal/config.d.ts +2 -2
  47. package/dist/portal/config.js +3 -3
  48. package/dist/portal/guard.d.ts +4 -4
  49. package/dist/portal/guard.js +4 -4
  50. package/dist/portal/nav.js +2 -2
  51. package/dist/portal/scaffold.d.ts +4 -4
  52. package/dist/portal/scaffold.js +6 -6
  53. package/dist/portal/session.d.ts +2 -2
  54. package/dist/portal/session.js +5 -5
  55. package/dist/portfolio/scaffold.d.ts +1 -1
  56. package/dist/portfolio/scaffold.js +4 -4
  57. package/dist/project/actions.d.ts +1 -1
  58. package/dist/project/actions.js +6 -6
  59. package/dist/project/generate.d.ts +4 -4
  60. package/dist/project/generate.js +15 -15
  61. package/dist/project/index.js +1 -1
  62. package/dist/project/scaffold.d.ts +2 -2
  63. package/dist/project/scaffold.js +11 -11
  64. package/dist/pwa/generate.d.ts +11 -11
  65. package/dist/pwa/generate.js +12 -12
  66. package/dist/queues/consumer.d.ts +3 -3
  67. package/dist/queues/consumer.js +2 -2
  68. package/dist/queues/messages.d.ts +4 -4
  69. package/dist/queues/messages.js +2 -2
  70. package/dist/queues/scaffold.d.ts +4 -4
  71. package/dist/queues/scaffold.js +8 -8
  72. package/dist/queues/webhook.d.ts +5 -5
  73. package/dist/queues/webhook.js +3 -3
  74. package/dist/realtime/scaffold.d.ts +4 -4
  75. package/dist/realtime/scaffold.js +8 -8
  76. package/dist/schema/collections.d.ts +6 -6
  77. package/dist/schema/collections.js +23 -23
  78. package/dist/schema/framework.d.ts +1 -1
  79. package/dist/schema/framework.js +2 -2
  80. package/dist/schema/generate.js +2 -2
  81. package/dist/schema/index.js +1 -1
  82. package/dist/secrets.d.ts +6 -6
  83. package/dist/secrets.js +6 -6
  84. package/dist/security/csp-origins.d.ts +1 -1
  85. package/dist/security/csp-origins.js +3 -3
  86. package/dist/security/rate-rules.d.ts +2 -2
  87. package/dist/security/rate-rules.js +8 -8
  88. package/dist/seo/resolve.d.ts +5 -5
  89. package/dist/seo/resolve.js +2 -2
  90. package/dist/seo/routes.d.ts +5 -5
  91. package/dist/seo/routes.js +3 -3
  92. package/dist/seo/structured-data.d.ts +6 -6
  93. package/dist/seo/structured-data.js +7 -7
  94. package/dist/status.d.ts +5 -5
  95. package/dist/status.js +7 -7
  96. package/dist/tenancy/index.d.ts +3 -3
  97. package/dist/tenancy/index.js +6 -6
  98. package/dist/worker/generate.d.ts +2 -2
  99. package/dist/worker/generate.js +19 -19
  100. package/dist/worker/index.js +1 -1
  101. package/dist/worker/routes.js +1 -1
  102. package/dist/workflow/advance.d.ts +3 -3
  103. package/dist/workflow/advance.js +6 -6
  104. package/dist/workflow/config.d.ts +4 -4
  105. package/dist/workflow/config.js +4 -4
  106. package/dist/workflow/generate.d.ts +2 -2
  107. package/dist/workflow/generate.js +4 -4
  108. package/package.json +3 -4
  109. package/src/components/Collection.tsx +5 -5
  110. package/src/components/Editable.astro +9 -9
  111. package/src/components/JustifiedGallery.astro +8 -8
  112. package/src/components/MediaSlot.astro +12 -12
  113. package/src/components/PortalShell.astro +4 -4
  114. package/src/components/RegisterSW.astro +3 -3
  115. package/src/components/Section.astro +8 -8
  116. package/src/components/Sections.astro +6 -6
  117. package/src/components/Seo.astro +3 -3
  118. package/src/components/StageBar.astro +3 -3
  119. package/src/components/StructuredData.astro +2 -2
  120. package/src/components/justify.ts +9 -9
  121. package/src/components/media-meta.ts +10 -10
  122. package/src/components/sections/AboutIntro.astro +1 -1
  123. package/src/components/sections/Contact.astro +1 -1
  124. package/src/components/sections/Cta.astro +1 -1
  125. package/src/components/sections/Faq.astro +1 -1
  126. package/src/components/sections/FeatureGrid.astro +2 -2
  127. package/src/components/sections/Hero.astro +1 -1
  128. package/src/components/sections/PricingTiers.astro +1 -1
  129. package/src/components/sections/ProductGrid.astro +1 -1
  130. package/src/components/sections/SplitImage.astro +1 -1
  131. package/src/components/sections/Steps.astro +1 -1
  132. package/src/components/sections/Testimonial.astro +1 -1
  133. package/src/components/sections.ts +17 -17
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // SEO resolution — settings defaults + per-page overrides, collapsed into the
3
+ // SEO resolution—settings defaults + per-page overrides, collapsed into the
4
4
  // exact set of values a `<head>` needs.
5
5
  //
6
6
  // Both sites that hand-built this layer converged on the same three-level
@@ -14,7 +14,7 @@
14
14
  // Kept as a pure function rather than baked into the component so it can be
15
15
  // unit-tested, and so a route that isn't rendering a page (an OG-image endpoint,
16
16
  // a feed) can resolve the same values.
17
- /** Trimmed value, or undefined — an empty/whitespace string counts as unset. */
17
+ /** Trimmed value, or undefined—an empty/whitespace string counts as unset. */
18
18
  const clean = (value) => {
19
19
  const trimmed = value?.trim();
20
20
  return trimmed ? trimmed : undefined;
@@ -4,14 +4,14 @@ import type { AstroidConfig } from "../config.js";
4
4
  * API always, plus the portal's account + auth surfaces and checkout when those
5
5
  * are enabled.
6
6
  *
7
- * These are *prefixes* — `robots.txt` matches by prefix, so `/api/` covers every
7
+ * These are *prefixes*—`robots.txt` matches by prefix, so `/api/` covers every
8
8
  * endpoint beneath it.
9
9
  */
10
10
  export declare function astroidNoindexPaths(config: AstroidConfig): string[];
11
11
  export interface RobotsOptions {
12
- /** Serving origin, e.g. `new URL(request.url).origin`. */
12
+ /** Serving origin, for example, `new URL(request.url).origin`. */
13
13
  origin: string;
14
- /** Paths to disallow — defaults to {@link astroidNoindexPaths}. */
14
+ /** Paths to disallow—defaults to {@link astroidNoindexPaths}. */
15
15
  disallow?: string[];
16
16
  /**
17
17
  * Disallow the entire site. Pass `settings.disableIndexing` so the same
@@ -25,7 +25,7 @@ export declare function astroidRobotsTxt(config: AstroidConfig, options: RobotsO
25
25
  export interface SitemapEntry {
26
26
  /** Site-root-relative path (`"/shop/beans"`) or an absolute URL. */
27
27
  path: string;
28
- /** Last modified — a Date or an ISO string. */
28
+ /** Last modified—a Date or an ISO string. */
29
29
  lastmod?: Date | string;
30
30
  }
31
31
  export interface SitemapOptions {
@@ -38,7 +38,7 @@ export interface SitemapOptions {
38
38
  *
39
39
  * Entries are de-duplicated and sorted (a stable document diffs cleanly and
40
40
  * caches predictably), excluded paths are dropped by prefix, and every `loc` is
41
- * XML-escaped — a slug containing `&` would otherwise produce a malformed
41
+ * XML-escaped—a slug containing `&` would otherwise produce a malformed
42
42
  * document that search engines reject wholesale.
43
43
  */
44
44
  export declare function astroidSitemapXml(config: AstroidConfig, entries: (SitemapEntry | string)[], options: SitemapOptions): string;
@@ -18,7 +18,7 @@ import { ASTROID_PORTAL_BASE_PATH } from "../security/rate-rules.js";
18
18
  * API always, plus the portal's account + auth surfaces and checkout when those
19
19
  * are enabled.
20
20
  *
21
- * These are *prefixes* — `robots.txt` matches by prefix, so `/api/` covers every
21
+ * These are *prefixes*—`robots.txt` matches by prefix, so `/api/` covers every
22
22
  * endpoint beneath it.
23
23
  */
24
24
  export function astroidNoindexPaths(config) {
@@ -32,7 +32,7 @@ export function astroidNoindexPaths(config) {
32
32
  paths.push(ASTROID_PORTAL_BASE_PATH, "/account", "/login", "/register", "/reset-password");
33
33
  }
34
34
  if (config.commerce) {
35
- // The checkout PAGE, not `ASTROID_CHECKOUT_PATH` — that's the POST endpoint,
35
+ // The checkout PAGE, not `ASTROID_CHECKOUT_PATH`—that's the POST endpoint,
36
36
  // already covered by the `/api/` prefix. What a crawler would actually reach
37
37
  // is the UI route.
38
38
  paths.push("/checkout", "/cart");
@@ -68,7 +68,7 @@ const escapeXml = (value) => value.replace(/[&<>"']/g, (c) => XML_ESCAPES[c] ??
68
68
  *
69
69
  * Entries are de-duplicated and sorted (a stable document diffs cleanly and
70
70
  * caches predictably), excluded paths are dropped by prefix, and every `loc` is
71
- * XML-escaped — a slug containing `&` would otherwise produce a malformed
71
+ * XML-escaped—a slug containing `&` would otherwise produce a malformed
72
72
  * document that search engines reject wholesale.
73
73
  */
74
74
  export function astroidSitemapXml(config, entries, options) {
@@ -6,7 +6,7 @@ export type JsonLdNode = Record<string, unknown>;
6
6
  * The schema.org `@type` each archetype describes its owner with. These are
7
7
  * intentionally the broad parent types: pick a subtype (`CafeOrCoffeeShop`,
8
8
  * `ArtGallery`, `HomeAndConstructionBusiness`) via `seo.businessType` when you
9
- * know one — a more specific type is strictly better for rich results.
9
+ * know one—a more specific type is strictly better for rich results.
10
10
  */
11
11
  export declare const ARCHETYPE_BUSINESS_TYPE: Record<Archetype, string>;
12
12
  export interface StructuredDataInput {
@@ -22,7 +22,7 @@ export interface StructuredDataInput {
22
22
  /** Absolute origin serving this page (the canonical host). */
23
23
  siteUrl: string;
24
24
  /**
25
- * An extra node for the thing this page is *about* — a Product, a
25
+ * An extra node for the thing this page is *about*—a Product, a
26
26
  * VisualArtwork, an Article. Joined into the same `@graph` so crawlers see
27
27
  * one connected description rather than three unrelated blobs.
28
28
  */
@@ -32,8 +32,8 @@ export interface StructuredDataInput {
32
32
  * Build the JSON-LD `@graph` for a page: the business node, a `WebSite` node,
33
33
  * and the page's own entity when there is one.
34
34
  *
35
- * The business gets a stable `@id` (`<origin>/#business`) so other nodes — a
36
- * product's `seller`, a future `Article` author — can reference it by id
35
+ * The business gets a stable `@id` (`<origin>/#business`) so other nodes—a
36
+ * product's `seller`, a future `Article` author—can reference it by id
37
37
  * instead of restating it.
38
38
  */
39
39
  export declare function astroidStructuredData(input: StructuredDataInput): JsonLdNode;
@@ -42,8 +42,8 @@ export declare function astroidStructuredData(input: StructuredDataInput): JsonL
42
42
  *
43
43
  * `application/ld+json` is data, not executable script, so `script-src` doesn't
44
44
  * govern it and no CSP hash is needed. But `JSON.stringify` does **not** escape
45
- * `<`, so any value folded into the graph that contains a literal `</script>` —
46
- * a product description, an artist statement, anything editor-authored — would
45
+ * `<`, so any value folded into the graph that contains a literal `</script>`—a
46
+ * product description, an artist statement, anything editor-authored—would
47
47
  * close the tag early and inject markup straight into `<head>`. Escaping the
48
48
  * HTML-significant characters as `\uXXXX` keeps the payload valid JSON while
49
49
  * making it impossible to break out of the element.
@@ -1,17 +1,17 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // JSON-LD structured data — the `@graph` that describes the business to rich
3
+ // JSON-LD structured data—the `@graph` that describes the business to rich
4
4
  // results and AI answer surfaces.
5
5
  //
6
6
  // Everything here is generic except the business `@type`, which is the one thing
7
- // that genuinely differs per site — so it comes from the archetype, with a
7
+ // that genuinely differs per site—so it comes from the archetype, with a
8
8
  // config escape hatch for the many cases where schema.org has a more specific
9
9
  // subtype (a coffee shop is a `CafeOrCoffeeShop`, not a bare `Store`).
10
10
  /**
11
11
  * The schema.org `@type` each archetype describes its owner with. These are
12
12
  * intentionally the broad parent types: pick a subtype (`CafeOrCoffeeShop`,
13
13
  * `ArtGallery`, `HomeAndConstructionBusiness`) via `seo.businessType` when you
14
- * know one — a more specific type is strictly better for rich results.
14
+ * know one—a more specific type is strictly better for rich results.
15
15
  */
16
16
  export const ARCHETYPE_BUSINESS_TYPE = {
17
17
  marketing: "Organization",
@@ -49,8 +49,8 @@ function sameAs(links) {
49
49
  * Build the JSON-LD `@graph` for a page: the business node, a `WebSite` node,
50
50
  * and the page's own entity when there is one.
51
51
  *
52
- * The business gets a stable `@id` (`<origin>/#business`) so other nodes — a
53
- * product's `seller`, a future `Article` author — can reference it by id
52
+ * The business gets a stable `@id` (`<origin>/#business`) so other nodes—a
53
+ * product's `seller`, a future `Article` author—can reference it by id
54
54
  * instead of restating it.
55
55
  */
56
56
  export function astroidStructuredData(input) {
@@ -91,8 +91,8 @@ export function astroidStructuredData(input) {
91
91
  *
92
92
  * `application/ld+json` is data, not executable script, so `script-src` doesn't
93
93
  * govern it and no CSP hash is needed. But `JSON.stringify` does **not** escape
94
- * `<`, so any value folded into the graph that contains a literal `</script>` —
95
- * a product description, an artist statement, anything editor-authored — would
94
+ * `<`, so any value folded into the graph that contains a literal `</script>`—a
95
+ * product description, an artist statement, anything editor-authored—would
96
96
  * close the tag early and inject markup straight into `<head>`. Escaping the
97
97
  * HTML-significant characters as `\uXXXX` keeps the payload valid JSON while
98
98
  * making it impossible to break out of the element.
package/dist/status.d.ts CHANGED
@@ -4,7 +4,7 @@ import type { SecretSource } from "./secrets.js";
4
4
  /**
5
5
  * Secrets every Astroid site has, independent of which modules are on.
6
6
  *
7
- * `SESSION_SECRET` is here but is NOT a dormancy gate — it fails closed off
7
+ * `SESSION_SECRET` is here but is NOT a dormancy gate—it fails closed off
8
8
  * localhost (see `getSessionSecret`), because an unsigned session isn't a
9
9
  * feature to switch off. It's listed so the scaffold seeds and types it.
10
10
  */
@@ -19,7 +19,7 @@ export interface AstroidModuleReport {
19
19
  configured: boolean;
20
20
  /** Unprovisioned secret/binding names, in declaration order. */
21
21
  missing: string[];
22
- /** What the module does in this state — the sentence a banner prints. */
22
+ /** What the module does in this state—the sentence a banner prints. */
23
23
  detail: string;
24
24
  }
25
25
  /**
@@ -27,7 +27,7 @@ export interface AstroidModuleReport {
27
27
  *
28
28
  * The scaffold uses this twice: to seed `.dev.vars`/`.env.example` with the
29
29
  * placeholder sentinel, and to type the matching `CloudflareEnv` members. A
30
- * module that isn't enabled contributes nothing — a declaration is a promise,
30
+ * module that isn't enabled contributes nothing—a declaration is a promise,
31
31
  * and a marketing site shouldn't be told to provision a Square token.
32
32
  */
33
33
  export declare function astroidSecretNames(config: AstroidConfig): Record<string, string[]>;
@@ -45,7 +45,7 @@ export type AstroidStatusEnv = MailerEnv & Record<string, SecretSource | unknown
45
45
  export declare function astroidModuleStatus(config: AstroidConfig, env: AstroidStatusEnv): Promise<AstroidModuleReport[]>;
46
46
  /**
47
47
  * The report as a printable block. One line per module, missing names spelled
48
- * out — "commerce is off" sends someone reading source; "commerce is dormant —
49
- * set SQUARE_ACCESS_TOKEN" does not.
48
+ * out—"commerce is off" sends someone reading source; "commerce is dormant—set
49
+ * SQUARE_ACCESS_TOKEN" does not.
50
50
  */
51
51
  export declare function describeAstroidStatus(reports: AstroidModuleReport[]): string;
package/dist/status.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // The module status report — "what is actually switched on right now".
3
+ // The module status report—"what is actually switched on right now".
4
4
  //
5
5
  // `secrets.ts` gives one module its gate. This composes every enabled module's
6
6
  // gate into one answer, which is what the dormant-until-provisioned convention
7
7
  // needs to be usable rather than merely available: a fresh scaffold boots with
8
8
  // nothing provisioned, and the failure mode that convention exists to avoid is
9
- // not a crash — it's a developer wondering for twenty minutes why the contact
9
+ // not a crash—it's a developer wondering for twenty minutes why the contact
10
10
  // form "works" but no mail arrives.
11
11
  //
12
12
  // So the deal is: dormant is fine, dormant AND SILENT is not. Two consumers of
@@ -22,7 +22,7 @@ import { EMAIL_SECRET_NAMES, resolveMailerStatus } from "./email/send.js";
22
22
  /**
23
23
  * Secrets every Astroid site has, independent of which modules are on.
24
24
  *
25
- * `SESSION_SECRET` is here but is NOT a dormancy gate — it fails closed off
25
+ * `SESSION_SECRET` is here but is NOT a dormancy gate—it fails closed off
26
26
  * localhost (see `getSessionSecret`), because an unsigned session isn't a
27
27
  * feature to switch off. It's listed so the scaffold seeds and types it.
28
28
  */
@@ -36,7 +36,7 @@ export const ASTROID_CORE_SECRET_NAMES = [
36
36
  *
37
37
  * The scaffold uses this twice: to seed `.dev.vars`/`.env.example` with the
38
38
  * placeholder sentinel, and to type the matching `CloudflareEnv` members. A
39
- * module that isn't enabled contributes nothing — a declaration is a promise,
39
+ * module that isn't enabled contributes nothing—a declaration is a promise,
40
40
  * and a marketing site shouldn't be told to provision a Square token.
41
41
  */
42
42
  export function astroidSecretNames(config) {
@@ -47,7 +47,7 @@ export function astroidSecretNames(config) {
47
47
  const commerce = commerceSecretNames(config.commerce);
48
48
  if (commerce.length > 0)
49
49
  groups.commerce = commerce;
50
- // The CWV read-back's API credentials. Collection needs none of this — only
50
+ // The CWV read-back's API credentials. Collection needs none of this—only
51
51
  // querying the p75 back out does, because the Analytics Engine SQL API is
52
52
  // account-scoped and has no binding.
53
53
  groups.vitals = [...ASTROID_VITALS_SECRET_NAMES];
@@ -96,8 +96,8 @@ export async function astroidModuleStatus(config, env) {
96
96
  }
97
97
  /**
98
98
  * The report as a printable block. One line per module, missing names spelled
99
- * out — "commerce is off" sends someone reading source; "commerce is dormant —
100
- * set SQUARE_ACCESS_TOKEN" does not.
99
+ * out—"commerce is off" sends someone reading source; "commerce is dormant—set
100
+ * SQUARE_ACCESS_TOKEN" does not.
101
101
  */
102
102
  export function describeAstroidStatus(reports) {
103
103
  if (reports.length === 0)
@@ -30,7 +30,7 @@ export declare function tenancyZone(tenancy: TenancyConfig): string;
30
30
  *
31
31
  * `null` covers four distinct cases that all mean "not a tenant": the apex
32
32
  * itself (a wildcard does not match its own apex), a host outside the pattern
33
- * (a preview domain, `localhost`), a reserved label, and an app label — which
33
+ * (a preview domain, `localhost`), a reserved label, and an app label—which
34
34
  * has its own static rewrite via {@link appPrefix} instead of a lookup.
35
35
  *
36
36
  * Exported and pure so a site can unit-test its own reserved list without
@@ -39,7 +39,7 @@ export declare function tenancyZone(tenancy: TenancyConfig): string;
39
39
  export declare function tenantLabel(host: string, tenancy: TenancyConfig): string | null;
40
40
  /**
41
41
  * The internal path prefix an app host rewrites to, or `null` when the host is
42
- * not an app host — `appPrefix("studio.example.com", …)` → `"/studio"` under
42
+ * not an app host—`appPrefix("studio.example.com", …)` → `"/studio"` under
43
43
  * `apps: { studio: "/studio" }`.
44
44
  *
45
45
  * Static by design: an app exists whether or not any tenant does, so there is
@@ -49,7 +49,7 @@ export declare function tenantLabel(host: string, tenancy: TenancyConfig): strin
49
49
  */
50
50
  export declare function appPrefix(host: string, tenancy: TenancyConfig): string | null;
51
51
  /**
52
- * The scaffold-once `src/tenancy.ts` — the seam holding every decision Astroid
52
+ * The scaffold-once `src/tenancy.ts`—the seam holding every decision Astroid
53
53
  * refuses to make for a site.
54
54
  *
55
55
  * Written once and then yours: what a label resolves to, whether the lookup is
@@ -3,7 +3,7 @@
3
3
  // Wildcard host dispatch: the parts that are the same for every site, and
4
4
  // nothing that decides anything.
5
5
  //
6
- // Astroid owns two things a site cannot own on its own — the wildcard Worker
6
+ // Astroid owns two things a site cannot own on its own—the wildcard Worker
7
7
  // route (`hosts` can only express custom domains) and the single middleware file
8
8
  // Astro permits. What a subdomain MEANS, whether the lookup is cached, and what
9
9
  // an unknown host should do are all site policy, and live in the scaffolded
@@ -38,7 +38,7 @@ export function tenancyZone(tenancy) {
38
38
  return tenancy.zone ?? tenancy.hostPattern.replace(/^\*\./, "");
39
39
  }
40
40
  /**
41
- * The single subdomain label under the wildcard, before any policy — or `null`
41
+ * The single subdomain label under the wildcard, before any policy—or `null`
42
42
  * for the apex, an off-pattern host, or a dotted label. Shared by
43
43
  * {@link tenantLabel} and {@link appPrefix} so their host handling (port,
44
44
  * case, one-level-only) cannot drift.
@@ -51,7 +51,7 @@ function hostLabel(host, tenancy) {
51
51
  return null;
52
52
  const label = hostname.slice(0, -(suffix.length + 1));
53
53
  // Only a single label counts. `a.b.example.com` under `*.example.com` is
54
- // not `a.b` — Cloudflare's wildcard matches one level, and treating a dotted
54
+ // not `a.b`—Cloudflare's wildcard matches one level, and treating a dotted
55
55
  // string as a slug would put a `/` in a rewrite path.
56
56
  if (!label || label.includes("."))
57
57
  return null;
@@ -63,7 +63,7 @@ function hostLabel(host, tenancy) {
63
63
  *
64
64
  * `null` covers four distinct cases that all mean "not a tenant": the apex
65
65
  * itself (a wildcard does not match its own apex), a host outside the pattern
66
- * (a preview domain, `localhost`), a reserved label, and an app label — which
66
+ * (a preview domain, `localhost`), a reserved label, and an app label—which
67
67
  * has its own static rewrite via {@link appPrefix} instead of a lookup.
68
68
  *
69
69
  * Exported and pure so a site can unit-test its own reserved list without
@@ -79,7 +79,7 @@ export function tenantLabel(host, tenancy) {
79
79
  }
80
80
  /**
81
81
  * The internal path prefix an app host rewrites to, or `null` when the host is
82
- * not an app host — `appPrefix("studio.example.com", …)` → `"/studio"` under
82
+ * not an app host—`appPrefix("studio.example.com", …)` → `"/studio"` under
83
83
  * `apps: { studio: "/studio" }`.
84
84
  *
85
85
  * Static by design: an app exists whether or not any tenant does, so there is
@@ -95,7 +95,7 @@ export function appPrefix(host, tenancy) {
95
95
  return label ? (apps[label] ?? null) : null;
96
96
  }
97
97
  /**
98
- * The scaffold-once `src/tenancy.ts` — the seam holding every decision Astroid
98
+ * The scaffold-once `src/tenancy.ts`—the seam holding every decision Astroid
99
99
  * refuses to make for a site.
100
100
  *
101
101
  * Written once and then yours: what a label resolves to, whether the lookup is
@@ -14,7 +14,7 @@ export declare function generateAstroidWorker(config: AstroidConfig): string;
14
14
  * session + sticky `?louise` edit mode → content-freshness + security headers) via
15
15
  * `createLouiseMiddleware`.
16
16
  *
17
- * The rate rules are NOT emitted as literals here — the file calls
17
+ * The rate rules are NOT emitted as literals here—the file calls
18
18
  * `astroidRateRules(astroidConfig)`, so the set stays real data in the package
19
19
  * (testable, and a `match` predicate survives, which a serialized literal could
20
20
  * not). Enabling a portal or commerce in the config adds that surface's rules
@@ -24,7 +24,7 @@ export declare function generateAstroidWorker(config: AstroidConfig): string;
24
24
  * Astro emits a hash-based `content-security-policy` response header on every SSR
25
25
  * page and owns `script-src`. The `cspStyleSrc` below tells
26
26
  * `createLouiseMiddleware` to rewrite that header's `style-src` to
27
- * `'self' 'unsafe-inline'` — a hash-based `style-src` would, per spec, void the
27
+ * `'self' 'unsafe-inline'`—a hash-based `style-src` would, per spec, void the
28
28
  * `'unsafe-inline'` that Louise's data-driven `style=""` carriers and the
29
29
  * editor's runtime-injected `<style>` require. Script hashes are left verbatim,
30
30
  * and the inlined `data:` brand font is auto-allowed.
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // generateAstroidWorker / generateAstroidMiddleware — emit the Cloudflare Worker
3
+ // generateAstroidWorker / generateAstroidMiddleware—emit the Cloudflare Worker
4
4
  // entrypoint and the Astro middleware a Louise site would otherwise hand-write.
5
5
  // The worker's editor routes are composed in the fixed order from the route plan
6
6
  // (routes.ts), so the "versionsRoute/searchRoute before pagesRoute" collision is
@@ -8,7 +8,7 @@
8
8
  //
9
9
  // One seam is marked with TODO(astroid) and filled by the auth slice: the
10
10
  // `resolveEditor` session resolver. The section-catalog validate + sanitize on
11
- // the pages routes is wired here — versionsRoute runs it through the collection's
11
+ // the pages routes is wired here—versionsRoute runs it through the collection's
12
12
  // beforeChange hook, and pagesRoute (which takes no collection config) through
13
13
  // the `astroidPagesWriteHooks` spread, so both write paths enforce one contract.
14
14
  import { ASTROID_VITALS_BINDING, generateAstroidCwvQuery } from "../analytics/index.js";
@@ -20,7 +20,7 @@ import { capturesInquiries } from "../schema/framework.js";
20
20
  import { astroidCspStyleSrc } from "../security/csp-origins.js";
21
21
  import { ASTROID_REWRITE_EXCLUDE, ASTROID_TENANT_PREFIX } from "../tenancy/index.js";
22
22
  import { astroidEditorRoutePlan } from "./routes.js";
23
- // Astroid's default editable site_settings surface — the columns the Settings
23
+ // Astroid's default editable site_settings surface—the columns the Settings
24
24
  // panel may write, and which of them hold a media-library image URL.
25
25
  //
26
26
  // EXPORTED because the generated worker is not the only consumer: the scaffolded
@@ -58,7 +58,7 @@ export function generateAstroidWorker(config) {
58
58
  const mediaBase = config.deploy?.mediaBase ?? "/media";
59
59
  const seedName = config.theme.name;
60
60
  const plan = astroidEditorRoutePlan(config);
61
- // `realtimeRoute` lives in `louise-toolkit/realtime`, not `/editor` — it is the
61
+ // `realtimeRoute` lives in `louise-toolkit/realtime`, not `/editor`—it is the
62
62
  // one factory in the plan that isn't an editor route. Importing it with the
63
63
  // rest type-checks fine HERE (the plan is just strings) and fails only in the
64
64
  // scaffold, which is exactly how it got caught.
@@ -80,7 +80,7 @@ export function generateAstroidWorker(config) {
80
80
  const routeCall = (name) => {
81
81
  switch (name) {
82
82
  case "overview":
83
- // `inbox` only when this project captures inquiries — an absent slice
83
+ // `inbox` only when this project captures inquiries—an absent slice
84
84
  // hides its card, which is right for an archetype with no contact form.
85
85
  return inquiries
86
86
  ? "overviewRoute({ resolveEditor, content: overviewContent, inbox: overviewInbox, health: overviewHealth })"
@@ -105,7 +105,7 @@ export function generateAstroidWorker(config) {
105
105
  case "save":
106
106
  // No `bufferKv` here, deliberately: `saveRoute` has no such option. It
107
107
  // writes live field saves (title, SEO) straight through, and the draft
108
- // buffer belongs to the versioned body — i.e. to versionsRoute.
108
+ // buffer belongs to the versioned body—that is, to versionsRoute.
109
109
  return 'saveRoute({ resolveEditor, collections: { pages: { table: pages, fields: ["title", "seoTitle", "seoDescription"] } } })';
110
110
  case "settings": {
111
111
  // Site-specific keys (config.settings.customKeys) are merged into
@@ -119,15 +119,15 @@ export function generateAstroidWorker(config) {
119
119
  // `aiRunner` rather than `(env) => env.AI`: it reads the binding AND the
120
120
  // LOUISE_AI kill switch, so all three assists share one definition of
121
121
  // "is generation on?" instead of each re-deriving it. Embeddings keep
122
- // binding-presence as their switch — see the helper's comment.
122
+ // binding-presence as their switch—see the helper's comment.
123
123
  case "ai":
124
124
  return "aiRoute({ resolveEditor, ai: aiRunner })";
125
125
  case "seoFix":
126
126
  return "seoFixRoute({ table: pages, resolveEditor, ai: aiRunner })";
127
127
  case "media": {
128
128
  // `altText` fills a new upload's alt from the image itself. Best-effort
129
- // by contract — a model error or a missing binding never fails the
130
- // upload — so it costs nothing on a project that doesn't want it.
129
+ // by contract—a model error or a missing binding never fails the
130
+ // upload—so it costs nothing on a project that doesn't want it.
131
131
  //
132
132
  // `maxBytes` is emitted only when the site raised it: omitted, the
133
133
  // route keeps louise-toolkit's DEFAULT_MAX_BYTES, so the generated
@@ -138,7 +138,7 @@ export function generateAstroidWorker(config) {
138
138
  }
139
139
  case "editors":
140
140
  // The editor instance's user table is `louise_`-prefixed (the editor
141
- // convention — the unprefixed `user` table is left for a second/portal
141
+ // convention—the unprefixed `user` table is left for a second/portal
142
142
  // instance). This route takes the table NAME, matching the
143
143
  // `tablePrefix` the scaffolded `src/auth.ts` passes to `getLouiseAuth`.
144
144
  return `editorsRoute({ table: ${JSON.stringify(astroidEditorTable("user"))}, resolveEditor })`;
@@ -174,7 +174,7 @@ export function generateAstroidWorker(config) {
174
174
  p('import { defineForm } from "louise-toolkit/forms";');
175
175
  if (queues)
176
176
  p('import { processBatch } from "louise-toolkit/queues";');
177
- // Only when a route actually takes a runner — a project with no AI assists
177
+ // Only when a route actually takes a runner—a project with no AI assists
178
178
  // should not import one, and knip would flag it if it did.
179
179
  if (plan.some((route) => AI_ROUTES.has(route.name))) {
180
180
  p('import { aiRunner } from "louise-toolkit/ai";');
@@ -408,7 +408,7 @@ export function generateAstroidWorker(config) {
408
408
  }
409
409
  // ONE scheduled handler for every cron, dispatching on `controller.cron`.
410
410
  // Cloudflare gives no other way to tell them apart, and the strings here have
411
- // to match `astroidCrons` exactly — which is why both read the same constants
411
+ // to match `astroidCrons` exactly—which is why both read the same constants
412
412
  // rather than repeating a literal.
413
413
  p(" // Cron. Cloudflare fires this for EVERY trigger in wrangler.jsonc and");
414
414
  p(" // identifies which by `controller.cron`, so dispatch on it.");
@@ -444,7 +444,7 @@ export function generateAstroidWorker(config) {
444
444
  p();
445
445
  if (usesRealtime(config)) {
446
446
  // Re-exported from the ENTRY because wrangler resolves a Durable Object
447
- // binding's `class_name` against the worker's exports — the class living in
447
+ // binding's `class_name` against the worker's exports—the class living in
448
448
  // src/edit-session.ts is not enough on its own, and the failure is a deploy
449
449
  // error about an unresolvable class rather than anything pointing here.
450
450
  p("// The realtime edit-session Durable Object. Re-exported so wrangler can");
@@ -460,7 +460,7 @@ export function generateAstroidWorker(config) {
460
460
  * session + sticky `?louise` edit mode → content-freshness + security headers) via
461
461
  * `createLouiseMiddleware`.
462
462
  *
463
- * The rate rules are NOT emitted as literals here — the file calls
463
+ * The rate rules are NOT emitted as literals here—the file calls
464
464
  * `astroidRateRules(astroidConfig)`, so the set stays real data in the package
465
465
  * (testable, and a `match` predicate survives, which a serialized literal could
466
466
  * not). Enabling a portal or commerce in the config adds that surface's rules
@@ -470,7 +470,7 @@ export function generateAstroidWorker(config) {
470
470
  * Astro emits a hash-based `content-security-policy` response header on every SSR
471
471
  * page and owns `script-src`. The `cspStyleSrc` below tells
472
472
  * `createLouiseMiddleware` to rewrite that header's `style-src` to
473
- * `'self' 'unsafe-inline'` — a hash-based `style-src` would, per spec, void the
473
+ * `'self' 'unsafe-inline'`—a hash-based `style-src` would, per spec, void the
474
474
  * `'unsafe-inline'` that Louise's data-driven `style=""` carriers and the
475
475
  * editor's runtime-injected `<style>` require. Script hashes are left verbatim,
476
476
  * and the inlined `data:` brand font is auto-allowed.
@@ -495,8 +495,8 @@ export function generateAstroidMiddleware(config) {
495
495
  "// styles + inlined data: brand font are allowed.",
496
496
  'import { env } from "cloudflare:workers";',
497
497
  'import { createLouiseMiddleware } from "@louise-toolkit/astro";',
498
- // One `astroidjs` import, composed from what this config actually uses —
499
- // two import statements for the same module is legal and reads as an
498
+ // One `astroidjs` import, composed from what this config actually uses—two
499
+ // import statements for the same module is legal and reads as an
500
500
  // oversight in a file nobody is supposed to hand-edit.
501
501
  `import { ${[
502
502
  ...(tenancy && Object.keys(tenancy.apps ?? {}).length ? ["appPrefix"] : []),
@@ -515,7 +515,7 @@ export function generateAstroidMiddleware(config) {
515
515
  "// TODO(astroid): your AUTH seam — same resolveEditor as the generated worker.ts.",
516
516
  'import { resolveEditor } from "./auth.js";',
517
517
  // The portal's resolver lives in its OWN module, not the editor's auth
518
- // seam — they're separate Better Auth instances and must not share a file.
518
+ // seam—they're separate Better Auth instances and must not share a file.
519
519
  ...(portal ? ['import { resolvePortalUser } from "./portal-auth.js";'] : []),
520
520
  "",
521
521
  "// Rate-limit the public, unauthenticated POST surface, keyed by client IP",
@@ -552,7 +552,7 @@ export function generateAstroidMiddleware(config) {
552
552
  " // anything the worker's routes didn't answer. A second check behind the",
553
553
  " // worker's gate, and free: the editor is resolved here on every request.",
554
554
  " apiGate: true,",
555
- // `extend` runs once and may need to populate BOTH — a tenanted site with a
555
+ // `extend` runs once and may need to populate BOTH—a tenanted site with a
556
556
  // portal resolves a tenant and a customer on the same request.
557
557
  ...(portal || tenancy
558
558
  ? [
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // Worker + middleware generation — config → the collision-free editor route plan
3
+ // Worker + middleware generation—config → the collision-free editor route plan
4
4
  // → the Worker entrypoint and Astro middleware a site would otherwise hand-write.
5
5
  export * from "./routes.js";
6
6
  export * from "./generate.js";
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // The editor route plan — which louise-toolkit/editor routes a project needs, in
3
+ // The editor route plan—which louise-toolkit/editor routes a project needs, in
4
4
  // the ONE order that avoids matcher collisions. This is where the "versionsRoute
5
5
  // and searchRoute MUST precede pagesRoute" tribal knowledge lives: encoded once,
6
6
  // as data, instead of re-derived by hand (and mis-ordered) in every site's
@@ -34,7 +34,7 @@ export interface AdvanceOptions {
34
34
  db: WorkflowDatabase;
35
35
  /** Table holding the `stage` column. */
36
36
  table: string;
37
- /** Audit table — one row per completed stage. */
37
+ /** Audit table—one row per completed stage. */
38
38
  auditTable: string;
39
39
  /** Primary key column on `table`. Default `"id"`. */
40
40
  idColumn?: string;
@@ -87,13 +87,13 @@ export interface OverrideOptions extends Omit<AdvanceOptions, "specs" | "expecte
87
87
  action: OverrideAction;
88
88
  /** Override log table. */
89
89
  overrideTable: string;
90
- /** Where the item is now, from the operator's page — the same staleness guard. */
90
+ /** Where the item is now, from the operator's page—the same staleness guard. */
91
91
  expectedStage: number;
92
92
  /** Optional station/context label recorded with the override. */
93
93
  station?: string;
94
94
  }
95
95
  /**
96
- * Move an item out of band — back a stage, or skip one — and log it.
96
+ * Move an item out of band—back a stage, or skip one—and log it.
97
97
  *
98
98
  * Sending an item BACK deletes the audit row for the stage being reopened, so
99
99
  * "a sign-off exists" keeps meaning "that stage is genuinely done". Leaving it
@@ -1,20 +1,20 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // The guarded advance — the one piece of a staged pipeline that is genuinely
3
+ // The guarded advance—the one piece of a staged pipeline that is genuinely
4
4
  // hard to get right.
5
5
  //
6
6
  // Two operators standing at two stations both press "sign off" on the same job.
7
7
  // A read-then-write advance runs the item forward two stages and writes two
8
8
  // audit rows, and nobody notices until the numbers stop adding up. The fix is
9
- // optimistic concurrency: make the write itself assert the stage it expected —
10
- // `UPDATE … SET stage = ? WHERE id = ? AND stage = ?` — and treat "0 rows
9
+ // optimistic concurrency: make the write itself assert the stage it
10
+ // expected—`UPDATE … SET stage = ? WHERE id = ? AND stage = ?`—and treat "0 rows
11
11
  // changed" as the conflict signal rather than checking first and hoping.
12
12
  //
13
13
  // ORDERING MATTERS, and the reference gets it wrong. ghostfire's floor route
14
14
  // inserts the sign-off row and THEN runs the guarded update, so a double submit
15
15
  // writes two audit rows even though only one advance lands. Here the guarded
16
16
  // update goes first and the audit row is written only if it actually moved the
17
- // item — so the audit table can't record work that didn't happen. The unique
17
+ // item—so the audit table can't record work that didn't happen. The unique
18
18
  // index the schema generator emits on `(entity_id, stage)` is the belt to that
19
19
  // braces.
20
20
  /** Longest initials we store. Three is what a floor actually writes. */
@@ -66,7 +66,7 @@ export async function advanceWorkflowStage(options) {
66
66
  }
67
67
  const next = expectedStage + 1;
68
68
  // The guard. `changes === 0` means the row is gone or someone else already
69
- // moved it — the two cases are told apart below, but only after the write,
69
+ // moved it—the two cases are told apart below, but only after the write,
70
70
  // so there is no window between the check and the update.
71
71
  const advanced = await db
72
72
  .prepare(`UPDATE ${table} SET stage = ? WHERE ${idColumn} = ? AND stage = ?`)
@@ -97,7 +97,7 @@ export async function advanceWorkflowStage(options) {
97
97
  return { ok: true, stage: next, complete: next >= stageCount };
98
98
  }
99
99
  /**
100
- * Move an item out of band — back a stage, or skip one — and log it.
100
+ * Move an item out of band—back a stage, or skip one—and log it.
101
101
  *
102
102
  * Sending an item BACK deletes the audit row for the stage being reopened, so
103
103
  * "a sign-off exists" keeps meaning "that stage is genuinely done". Leaving it
@@ -13,7 +13,7 @@ export interface WorkflowField {
13
13
  }
14
14
  export interface WorkflowConfig {
15
15
  /**
16
- * Base name for the generated tables and routes — `"orders"` gives an
16
+ * Base name for the generated tables and routes—`"orders"` gives an
17
17
  * `orders.stage` column, an `orders_signoffs` audit table, and
18
18
  * `/api/orders/advance`.
19
19
  */
@@ -24,15 +24,15 @@ export interface WorkflowConfig {
24
24
  * Per-stage fields recorded on sign-off, keyed by stage key. A stage with no
25
25
  * entry records only actor + timestamp.
26
26
  *
27
- * These are the "specs" in the reference — brew ratio, water activity. They
27
+ * These are the "specs" in the reference—brew ratio, water activity. They
28
28
  * are stored as a JSON blob on the audit row rather than as columns, because
29
29
  * they are documentation of what happened, not something the pipeline
30
30
  * branches on, and every stage wants a different set.
31
31
  */
32
32
  stationFields?: Record<string, WorkflowField[]>;
33
33
  /**
34
- * Emit an override log table. Every out-of-band move — sending an item back a
35
- * stage, skipping one — is recorded with the actor's initials. Default true:
34
+ * Emit an override log table. Every out-of-band move—sending an item back a
35
+ * stage, skipping one—is recorded with the actor's initials. Default true:
36
36
  * a pipeline you can override without a trace is one nobody trusts.
37
37
  */
38
38
  overrides?: boolean;