astroidjs 0.9.5 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
- import type { AstroidConfig, CspOrigins } from "../config.js";
1
+ import type { AstroidConfig } from "../config.js";
2
+ export { astroidCspOrigins } from "../security/csp-origins.js";
2
3
  /** A `sha256-…` CSP hash, in the shape Astro's `security.csp.hashes` takes. */
3
4
  export type CspHash = `${"sha256" | "sha384" | "sha512"}-${string}`;
4
5
  /**
@@ -38,12 +39,6 @@ export interface AstroidSecurityConfig {
38
39
  * hash on the next build.
39
40
  */
40
41
  export declare function solidHydrationHash(): CspHash;
41
- /**
42
- * Every origin the project's enabled modules need, merged with the config's own
43
- * `security.cspOrigins`. Exported so a site (or `astroid doctor`) can inspect
44
- * what a config implies without rebuilding the whole policy.
45
- */
46
- export declare function astroidCspOrigins(config: AstroidConfig): Required<CspOrigins>;
47
42
  /**
48
43
  * The `security` block for `astro.config.mjs` — Astro's half of the split.
49
44
  *
@@ -61,4 +56,3 @@ export declare function astroidSecurity(config: AstroidConfig): AstroidSecurityC
61
56
  export declare const ASTROID_VITE_BUILD: {
62
57
  readonly assetsInlineLimit: 0;
63
58
  };
64
- export {};
package/dist/astro/csp.js CHANGED
@@ -27,7 +27,8 @@
27
27
  // imports.
28
28
  import { createHash } from "node:crypto";
29
29
  import { generateHydrationScript } from "solid-js/web";
30
- import { astroidCommerceProviders } from "../commerce/roles.js";
30
+ import { astroidCspOrigins } from "../security/csp-origins.js";
31
+ export { astroidCspOrigins } from "../security/csp-origins.js";
31
32
  /**
32
33
  * Hash of Solid's inline hydration bootstrap.
33
34
  *
@@ -42,80 +43,6 @@ export function solidHydrationHash() {
42
43
  const inner = generateHydrationScript().match(/<script[^>]*>([\s\S]*?)<\/script>/)?.[1] ?? "";
43
44
  return `sha256-${createHash("sha256").update(inner).digest("base64")}`;
44
45
  }
45
- /** Origins a commerce provider's client-side SDK needs. Server-only providers
46
- * (Fourthwall's storefront API) contribute nothing but their image host. */
47
- const COMMERCE_ORIGINS = {
48
- // Square Web Payments renders the card form in an iframe from the squarecdn
49
- // hosts, tokenizes against pci-connect, and pulls its own fonts. Both the
50
- // sandbox and production hosts are listed so ONE build serves either
51
- // environment — which environment you're in is a runtime secret, not a
52
- // build-time one.
53
- square: {
54
- script: ["https://sandbox.web.squarecdn.com", "https://web.squarecdn.com"],
55
- frame: [
56
- "https://sandbox.web.squarecdn.com",
57
- "https://web.squarecdn.com",
58
- "https://connect.squareupsandbox.com",
59
- "https://connect.squareup.com",
60
- ],
61
- connect: [
62
- "https://pci-connect.squareupsandbox.com",
63
- "https://pci-connect.squareup.com",
64
- "https://sandbox.web.squarecdn.com",
65
- "https://web.squarecdn.com",
66
- ],
67
- font: [
68
- "https://square-fonts-production-f.squarecdn.com",
69
- "https://d1g145x70srn7h.cloudfront.net",
70
- ],
71
- },
72
- // Stripe.js and its Elements/Checkout iframes.
73
- stripe: {
74
- script: ["https://js.stripe.com"],
75
- frame: ["https://js.stripe.com", "https://hooks.stripe.com"],
76
- connect: ["https://api.stripe.com"],
77
- },
78
- // Fourthwall is read server-side; nothing runs in the browser.
79
- fourthwall: {},
80
- };
81
- // Turnstile. Always allowed, not gated on the captcha being configured: the
82
- // scaffold ships the widget dormant (see the dormant-until-provisioned
83
- // convention) and it must not need a rebuild to switch on — CSP is baked at
84
- // build time, the secret is a runtime value.
85
- const TURNSTILE = {
86
- script: ["https://challenges.cloudflare.com"],
87
- frame: ["https://challenges.cloudflare.com"],
88
- connect: ["https://challenges.cloudflare.com"],
89
- };
90
- // The map module. MapLibre spins its tile-decoding workers up from blob: URLs,
91
- // so `worker-src blob:` is not optional — without it the map renders an empty
92
- // canvas and the console fills with worker-construction errors.
93
- //
94
- // Nothing else is needed, and that's the whole argument for the self-hosted
95
- // basemap: the PMTiles archive is served same-origin, so `connect-src` stays
96
- // `'self'` with no tile host and no API key to allow.
97
- const MAP = { worker: ["blob:"] };
98
- const DIRECTIVE_KEYS = ["script", "frame", "connect", "font", "img", "worker"];
99
- /** Merge origin lists, de-duplicated, order preserved. */
100
- function mergeOrigins(...sets) {
101
- const out = {};
102
- for (const key of DIRECTIVE_KEYS) {
103
- out[key] = [...new Set(sets.flatMap((set) => set[key] ?? []))];
104
- }
105
- return out;
106
- }
107
- /**
108
- * Every origin the project's enabled modules need, merged with the config's own
109
- * `security.cspOrigins`. Exported so a site (or `astroid doctor`) can inspect
110
- * what a config implies without rebuilding the whole policy.
111
- */
112
- export function astroidCspOrigins(config) {
113
- return mergeOrigins(TURNSTILE, ...((config.modules ?? []).includes("map") ? [MAP] : []),
114
- // EVERY provider in play, not "the" provider: a site can run Stripe for
115
- // invoicing beside Fourthwall for the storefront, and a policy that allowed
116
- // only one of them blocks the other's SDK at runtime.
117
- ...astroidCommerceProviders(config.commerce).map((p) => COMMERCE_ORIGINS[p] ?? {}), config.security?.cspOrigins ?? {});
118
- }
119
46
  /**
120
47
  * Render one directive. The name is a literal from the union above, so the
121
48
  * concatenation is a valid `CspDirective` by construction — which is what the
@@ -1 +1 @@
1
- export { ASTROID_VITE_BUILD, astroidCspOrigins, astroidSecurity, type AstroidSecurityConfig, type CspHash, solidHydrationHash, } from "./csp.js";
1
+ export { ASTROID_VITE_BUILD, astroidCspOrigins, astroidSecurity, type AstroidSecurityConfig, type CspDirective, type CspHash, solidHydrationHash, } from "./csp.js";
package/dist/config.d.ts CHANGED
@@ -378,6 +378,8 @@ export interface SecurityConfig {
378
378
  /** Per-directive origin lists contributed to the CSP. */
379
379
  export interface CspOrigins {
380
380
  script?: string[];
381
+ /** Rendered by the generated middleware (it owns `style-src`), not by Astro. */
382
+ style?: string[];
381
383
  frame?: string[];
382
384
  connect?: string[];
383
385
  font?: string[];
@@ -0,0 +1,16 @@
1
+ import type { AstroidConfig, CspOrigins } from "../config.js";
2
+ /**
3
+ * Every origin the project's enabled modules need, merged with the config's own
4
+ * `security.cspOrigins`. Exported so a site (or `astroid doctor`) can inspect
5
+ * what a config implies without rebuilding the whole policy.
6
+ */
7
+ export declare function astroidCspOrigins(config: AstroidConfig): Required<CspOrigins>;
8
+ /**
9
+ * The `style-src` value the generated middleware rewrites every response to.
10
+ *
11
+ * `'unsafe-inline'` is required (Louise's data-driven `style=""` carriers and
12
+ * the editor's runtime `<style>`), which is why Astro can't own this directive:
13
+ * per spec a single hash here voids `'unsafe-inline'`. Module and config
14
+ * `style` origins ride along, e.g. the Square SDK's own stylesheet.
15
+ */
16
+ export declare function astroidCspStyleSrc(config: AstroidConfig): string;
@@ -0,0 +1,99 @@
1
+ // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
+ //
3
+ // The CSP origins a config implies, as plain data.
4
+ //
5
+ // Split out of `astro/csp.ts` (build-time only: `node:crypto`, `solid-js/web`)
6
+ // so the worker generator can read it too: Astro's `security.csp` renders every
7
+ // directive except `style-src`, and the generated middleware renders that one.
8
+ // Both have to see the same origin set, or a module's stylesheet host lands in
9
+ // one half of the policy and not the other.
10
+ import { astroidCommerceProviders } from "../commerce/roles.js";
11
+ /** Origins a commerce provider's client-side SDK needs. Server-only providers
12
+ * (Fourthwall's storefront API) contribute nothing but their image host. */
13
+ const COMMERCE_ORIGINS = {
14
+ // Square Web Payments renders the card form in an iframe from the squarecdn
15
+ // hosts, tokenizes against pci-connect, and pulls its own fonts. Both the
16
+ // sandbox and production hosts are listed so ONE build serves either
17
+ // environment — which environment you're in is a runtime secret, not a
18
+ // build-time one.
19
+ square: {
20
+ script: ["https://sandbox.web.squarecdn.com", "https://web.squarecdn.com"],
21
+ frame: [
22
+ "https://sandbox.web.squarecdn.com",
23
+ "https://web.squarecdn.com",
24
+ "https://connect.squareupsandbox.com",
25
+ "https://connect.squareup.com",
26
+ ],
27
+ connect: [
28
+ "https://pci-connect.squareupsandbox.com",
29
+ "https://pci-connect.squareup.com",
30
+ "https://sandbox.web.squarecdn.com",
31
+ "https://web.squarecdn.com",
32
+ ],
33
+ // Web Payments SDK 1.85+ attaches its own `card-wrapper.css` to the HOST
34
+ // page (not just inside the iframe). Block it and `card.attach()` rejects,
35
+ // so the card form never mounts.
36
+ style: ["https://sandbox.web.squarecdn.com", "https://web.squarecdn.com"],
37
+ font: [
38
+ "https://square-fonts-production-f.squarecdn.com",
39
+ "https://d1g145x70srn7h.cloudfront.net",
40
+ ],
41
+ },
42
+ // Stripe.js and its Elements/Checkout iframes.
43
+ stripe: {
44
+ script: ["https://js.stripe.com"],
45
+ frame: ["https://js.stripe.com", "https://hooks.stripe.com"],
46
+ connect: ["https://api.stripe.com"],
47
+ },
48
+ // Fourthwall is read server-side; nothing runs in the browser.
49
+ fourthwall: {},
50
+ };
51
+ // Turnstile. Always allowed, not gated on the captcha being configured: the
52
+ // scaffold ships the widget dormant (see the dormant-until-provisioned
53
+ // convention) and it must not need a rebuild to switch on — CSP is baked at
54
+ // build time, the secret is a runtime value.
55
+ const TURNSTILE = {
56
+ script: ["https://challenges.cloudflare.com"],
57
+ frame: ["https://challenges.cloudflare.com"],
58
+ connect: ["https://challenges.cloudflare.com"],
59
+ };
60
+ // The map module. MapLibre spins its tile-decoding workers up from blob: URLs,
61
+ // so `worker-src blob:` is not optional — without it the map renders an empty
62
+ // canvas and the console fills with worker-construction errors.
63
+ //
64
+ // Nothing else is needed, and that's the whole argument for the self-hosted
65
+ // basemap: the PMTiles archive is served same-origin, so `connect-src` stays
66
+ // `'self'` with no tile host and no API key to allow.
67
+ const MAP = { worker: ["blob:"] };
68
+ const DIRECTIVE_KEYS = ["script", "style", "frame", "connect", "font", "img", "worker"];
69
+ /** Merge origin lists, de-duplicated, order preserved. */
70
+ function mergeOrigins(...sets) {
71
+ const out = {};
72
+ for (const key of DIRECTIVE_KEYS) {
73
+ out[key] = [...new Set(sets.flatMap((set) => set[key] ?? []))];
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * Every origin the project's enabled modules need, merged with the config's own
79
+ * `security.cspOrigins`. Exported so a site (or `astroid doctor`) can inspect
80
+ * what a config implies without rebuilding the whole policy.
81
+ */
82
+ export function astroidCspOrigins(config) {
83
+ return mergeOrigins(TURNSTILE, ...((config.modules ?? []).includes("map") ? [MAP] : []),
84
+ // EVERY provider in play, not "the" provider: a site can run Stripe for
85
+ // invoicing beside Fourthwall for the storefront, and a policy that allowed
86
+ // only one of them blocks the other's SDK at runtime.
87
+ ...astroidCommerceProviders(config.commerce).map((p) => COMMERCE_ORIGINS[p] ?? {}), config.security?.cspOrigins ?? {});
88
+ }
89
+ /**
90
+ * The `style-src` value the generated middleware rewrites every response to.
91
+ *
92
+ * `'unsafe-inline'` is required (Louise's data-driven `style=""` carriers and
93
+ * the editor's runtime `<style>`), which is why Astro can't own this directive:
94
+ * per spec a single hash here voids `'unsafe-inline'`. Module and config
95
+ * `style` origins ride along, e.g. the Square SDK's own stylesheet.
96
+ */
97
+ export function astroidCspStyleSrc(config) {
98
+ return ["'self'", "'unsafe-inline'", ...astroidCspOrigins(config).style].join(" ");
99
+ }
@@ -17,6 +17,7 @@ import { astroidPortal } from "../portal/config.js";
17
17
  import { ASTROID_HEALTH_CRON, astroidCron, astroidUsesQueues } from "../queues/messages.js";
18
18
  import { ASTROID_EDIT_SESSION_CLASS, ASTROID_REALTIME_BINDING, usesRealtime, } from "../realtime/scaffold.js";
19
19
  import { capturesInquiries } from "../schema/framework.js";
20
+ import { astroidCspStyleSrc } from "../security/csp-origins.js";
20
21
  import { ASTROID_REWRITE_EXCLUDE, ASTROID_TENANT_PREFIX } from "../tenancy/index.js";
21
22
  import { astroidEditorRoutePlan } from "./routes.js";
22
23
  // Astroid's default editable site_settings surface — the columns the Settings
@@ -470,8 +471,10 @@ export function generateAstroidWorker(config) {
470
471
  export function generateAstroidMiddleware(config) {
471
472
  // Louise's brand font is bundled + base64-inlined (no Google Fonts host to
472
473
  // allow); createLouiseMiddleware auto-allows `data:` fonts in the CSP, so the
473
- // inlined @font-face needs no manual `font-src` entry.
474
- const cspStyleSrc = "'self' 'unsafe-inline'";
474
+ // inlined @font-face needs no manual `font-src` entry. Module + config `style`
475
+ // origins (the Square SDK's stylesheet) are baked in here; `astroid build`
476
+ // regenerates this file, so a config change lands on the next deploy.
477
+ const cspStyleSrc = astroidCspStyleSrc(config);
475
478
  const portal = astroidPortal(config);
476
479
  const tenancy = config.tenancy;
477
480
  const rewritePrefix = tenancy?.rewritePrefix ?? ASTROID_TENANT_PREFIX;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astroidjs",
3
- "version": "0.9.5",
3
+ "version": "0.10.1",
4
4
  "description": "Astroid — an opinionated meta-framework over Louise Toolkit and Astro for building editable, multi-editor sites on Cloudflare Workers.",
5
5
  "keywords": [
6
6
  "astro",
@@ -14,7 +14,7 @@
14
14
  "author": "BowenLabs",
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git+https://github.com/bowenlabs/louise-toolkit.git",
17
+ "url": "git+https://github.com/bowenlabs/astroidjs.git",
18
18
  "directory": "packages/astroid"
19
19
  },
20
20
  "bin": {
@@ -60,7 +60,7 @@
60
60
  "access": "public"
61
61
  },
62
62
  "dependencies": {
63
- "louise-toolkit": "0.27.0"
63
+ "louise-toolkit": "^0.28.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/node": "^24.13.3",
@@ -83,12 +83,11 @@
83
83
  }
84
84
  },
85
85
  "engines": {
86
- "node": ">=24.0.0"
86
+ "node": ">=26.0.0"
87
87
  },
88
88
  "scripts": {
89
89
  "build": "tsgo -p tsconfig.build.json",
90
- "check": "vp check",
91
- "test": "vp test",
90
+ "test": "vitest run",
92
91
  "typecheck": "tsgo --noEmit"
93
92
  }
94
93
  }