astro 5.8.1 → 5.9.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 (70) hide show
  1. package/dist/actions/runtime/utils.d.ts +1 -1
  2. package/dist/assets/fonts/config.d.ts +21 -21
  3. package/dist/container/index.js +2 -1
  4. package/dist/content/content-layer.js +14 -3
  5. package/dist/content/loaders/types.d.ts +3 -0
  6. package/dist/content/utils.d.ts +26 -10
  7. package/dist/content/utils.js +1 -0
  8. package/dist/core/app/types.d.ts +12 -1
  9. package/dist/core/base-pipeline.js +3 -3
  10. package/dist/core/build/generate.d.ts +1 -1
  11. package/dist/core/build/generate.js +38 -4
  12. package/dist/core/build/internal.d.ts +1 -0
  13. package/dist/core/build/internal.js +2 -1
  14. package/dist/core/build/plugins/index.js +1 -1
  15. package/dist/core/build/plugins/plugin-internals.d.ts +2 -1
  16. package/dist/core/build/plugins/plugin-internals.js +7 -4
  17. package/dist/core/build/plugins/plugin-manifest.js +37 -3
  18. package/dist/core/config/schemas/base.d.ts +435 -331
  19. package/dist/core/config/schemas/base.js +20 -2
  20. package/dist/core/config/schemas/index.d.ts +1 -1
  21. package/dist/core/config/schemas/index.js +4 -1
  22. package/dist/core/config/schemas/relative.d.ts +681 -552
  23. package/dist/core/constants.js +1 -1
  24. package/dist/core/csp/common.d.ts +16 -0
  25. package/dist/core/csp/common.js +116 -0
  26. package/dist/core/csp/config.d.ts +16 -0
  27. package/dist/core/csp/config.js +52 -0
  28. package/dist/core/dev/dev.js +1 -1
  29. package/dist/core/encryption.d.ts +8 -0
  30. package/dist/core/encryption.js +7 -0
  31. package/dist/core/errors/errors-data.d.ts +12 -0
  32. package/dist/core/errors/errors-data.js +6 -0
  33. package/dist/core/messages.js +2 -2
  34. package/dist/core/middleware/index.js +10 -0
  35. package/dist/core/middleware/sequence.js +2 -2
  36. package/dist/core/render-context.d.ts +1 -0
  37. package/dist/core/render-context.js +77 -5
  38. package/dist/env/schema.d.ts +34 -34
  39. package/dist/integrations/features-validation.js +36 -30
  40. package/dist/integrations/hooks.d.ts +3 -2
  41. package/dist/runtime/server/astro-island-styles.d.ts +1 -0
  42. package/dist/runtime/server/astro-island-styles.js +4 -0
  43. package/dist/runtime/server/index.d.ts +1 -0
  44. package/dist/runtime/server/render/astro/factory.d.ts +3 -2
  45. package/dist/runtime/server/render/astro/factory.js +6 -1
  46. package/dist/runtime/server/render/astro/head-and-content.d.ts +7 -0
  47. package/dist/runtime/server/render/astro/head-and-content.js +6 -0
  48. package/dist/runtime/server/render/astro/render.d.ts +1 -0
  49. package/dist/runtime/server/render/astro/render.js +2 -1
  50. package/dist/runtime/server/render/common.d.ts +1 -1
  51. package/dist/runtime/server/render/common.js +5 -3
  52. package/dist/runtime/server/render/component.js +8 -2
  53. package/dist/runtime/server/render/csp.d.ts +2 -0
  54. package/dist/runtime/server/render/csp.js +35 -0
  55. package/dist/runtime/server/render/head.js +14 -0
  56. package/dist/runtime/server/render/page.d.ts +1 -1
  57. package/dist/runtime/server/render/page.js +1 -1
  58. package/dist/runtime/server/render/server-islands.d.ts +14 -3
  59. package/dist/runtime/server/render/server-islands.js +100 -69
  60. package/dist/runtime/server/render/util.js +3 -0
  61. package/dist/runtime/server/scripts.d.ts +1 -1
  62. package/dist/runtime/server/scripts.js +2 -5
  63. package/dist/runtime/server/transition.d.ts +1 -1
  64. package/dist/runtime/server/transition.js +7 -2
  65. package/dist/types/public/config.d.ts +240 -0
  66. package/dist/types/public/context.d.ts +51 -0
  67. package/dist/types/public/integrations.d.ts +9 -0
  68. package/dist/types/public/internal.d.ts +24 -2
  69. package/dist/vite-plugin-astro-server/plugin.js +26 -4
  70. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
- import { encryptString } from "../../../core/encryption.js";
1
+ import { encryptString, generateCspDigest } from "../../../core/encryption.js";
2
2
  import { markHTMLString } from "../escape.js";
3
3
  import { renderChild } from "./any.js";
4
+ import { createThinHead } from "./astro/head-and-content.js";
4
5
  import { createRenderInstruction } from "./instruction.js";
5
6
  import { renderSlotToString } from "./slot.js";
6
7
  const internalProps = /* @__PURE__ */ new Set([
@@ -31,54 +32,63 @@ function isWithinURLLimit(pathname, params) {
31
32
  const chars = url.length;
32
33
  return chars < 2048;
33
34
  }
34
- function renderServerIsland(result, _displayName, props, slots) {
35
- return {
36
- async render(destination) {
37
- const componentPath = props["server:component-path"];
38
- const componentExport = props["server:component-export"];
39
- const componentId = result.serverIslandNameMap.get(componentPath);
40
- if (!componentId) {
41
- throw new Error(`Could not find server component name`);
42
- }
43
- for (const key2 of Object.keys(props)) {
44
- if (internalProps.has(key2)) {
45
- delete props[key2];
46
- }
35
+ class ServerIslandComponent {
36
+ result;
37
+ props;
38
+ slots;
39
+ displayName;
40
+ hostId;
41
+ islandContent;
42
+ constructor(result, props, slots, displayName) {
43
+ this.result = result;
44
+ this.props = props;
45
+ this.slots = slots;
46
+ this.displayName = displayName;
47
+ }
48
+ async init() {
49
+ const componentPath = this.props["server:component-path"];
50
+ const componentExport = this.props["server:component-export"];
51
+ const componentId = this.result.serverIslandNameMap.get(componentPath);
52
+ if (!componentId) {
53
+ throw new Error(`Could not find server component name`);
54
+ }
55
+ for (const key2 of Object.keys(this.props)) {
56
+ if (internalProps.has(key2)) {
57
+ delete this.props[key2];
47
58
  }
48
- destination.write(createRenderInstruction({ type: "server-island-runtime" }));
49
- destination.write("<!--[if astro]>server-island-start<![endif]-->");
50
- const renderedSlots = {};
51
- for (const name in slots) {
52
- if (name !== "fallback") {
53
- const content = await renderSlotToString(result, slots[name]);
54
- renderedSlots[name] = content.toString();
55
- } else {
56
- await renderChild(destination, slots.fallback(result));
57
- }
59
+ }
60
+ const renderedSlots = {};
61
+ for (const name in this.slots) {
62
+ if (name !== "fallback") {
63
+ const content2 = await renderSlotToString(this.result, this.slots[name]);
64
+ renderedSlots[name] = content2.toString();
58
65
  }
59
- const key = await result.key;
60
- const propsEncrypted = Object.keys(props).length === 0 ? "" : await encryptString(key, JSON.stringify(props));
61
- const hostId = crypto.randomUUID();
62
- const slash = result.base.endsWith("/") ? "" : "/";
63
- let serverIslandUrl = `${result.base}${slash}_server-islands/${componentId}${result.trailingSlash === "always" ? "/" : ""}`;
64
- const potentialSearchParams = createSearchParams(
65
- componentExport,
66
- propsEncrypted,
67
- safeJsonStringify(renderedSlots)
68
- );
69
- const useGETRequest = isWithinURLLimit(serverIslandUrl, potentialSearchParams);
70
- if (useGETRequest) {
71
- serverIslandUrl += "?" + potentialSearchParams.toString();
72
- destination.write(
66
+ }
67
+ const key = await this.result.key;
68
+ const propsEncrypted = Object.keys(this.props).length === 0 ? "" : await encryptString(key, JSON.stringify(this.props));
69
+ const hostId = crypto.randomUUID();
70
+ const slash = this.result.base.endsWith("/") ? "" : "/";
71
+ let serverIslandUrl = `${this.result.base}${slash}_server-islands/${componentId}${this.result.trailingSlash === "always" ? "/" : ""}`;
72
+ const potentialSearchParams = createSearchParams(
73
+ componentExport,
74
+ propsEncrypted,
75
+ safeJsonStringify(renderedSlots)
76
+ );
77
+ const useGETRequest = isWithinURLLimit(serverIslandUrl, potentialSearchParams);
78
+ if (useGETRequest) {
79
+ serverIslandUrl += "?" + potentialSearchParams.toString();
80
+ this.result._metadata.extraHead.push(
81
+ markHTMLString(
73
82
  `<link rel="preload" as="fetch" href="${serverIslandUrl}" crossorigin="anonymous">`
74
- );
75
- }
76
- destination.write(`<script type="module" data-astro-rerun data-island-id="${hostId}">${useGETRequest ? (
77
- // GET request
78
- `let response = await fetch('${serverIslandUrl}');`
79
- ) : (
80
- // POST request
81
- `let data = {
83
+ )
84
+ );
85
+ }
86
+ const method = useGETRequest ? (
87
+ // GET request
88
+ `let response = await fetch('${serverIslandUrl}');`
89
+ ) : (
90
+ // POST request
91
+ `let data = {
82
92
  componentExport: ${safeJsonStringify(componentExport)},
83
93
  encryptedProps: ${safeJsonStringify(propsEncrypted)},
84
94
  slots: ${safeJsonStringify(renderedSlots)},
@@ -87,33 +97,54 @@ let response = await fetch('${serverIslandUrl}', {
87
97
  method: 'POST',
88
98
  body: JSON.stringify(data),
89
99
  });`
90
- )}
91
- replaceServerIsland('${hostId}', response);</script>`);
100
+ );
101
+ const content = `${method}replaceServerIsland('${hostId}', response);`;
102
+ if (this.result.shouldInjectCspMetaTags) {
103
+ this.result._metadata.extraScriptHashes.push(
104
+ await generateCspDigest(SERVER_ISLAND_REPLACER, this.result.cspAlgorithm)
105
+ );
106
+ const contentDigest = await generateCspDigest(content, this.result.cspAlgorithm);
107
+ this.result._metadata.extraScriptHashes.push(contentDigest);
92
108
  }
93
- };
109
+ this.islandContent = content;
110
+ this.hostId = hostId;
111
+ return createThinHead();
112
+ }
113
+ async render(destination) {
114
+ for (const name in this.slots) {
115
+ if (name === "fallback") {
116
+ await renderChild(destination, this.slots.fallback(this.result));
117
+ }
118
+ }
119
+ destination.write(createRenderInstruction({ type: "server-island-runtime" }));
120
+ destination.write("<!--[if astro]>server-island-start<![endif]-->");
121
+ destination.write(
122
+ `<script type="module" data-astro-rerun data-island-id="${this.hostId}">${this.islandContent}</script>`
123
+ );
124
+ }
94
125
  }
95
- const renderServerIslandRuntime = () => markHTMLString(
96
- `
97
- <script>
98
- async function replaceServerIsland(id, r) {
99
- let s = document.querySelector(\`script[data-island-id="\${id}"]\`);
100
- // If there's no matching script, or the request fails then return
101
- if (!s || r.status !== 200 || r.headers.get('content-type')?.split(';')[0].trim() !== 'text/html') return;
102
- // Load the HTML before modifying the DOM in case of errors
103
- let html = await r.text();
104
- // Remove any placeholder content before the island script
105
- while (s.previousSibling && s.previousSibling.nodeType !== 8 && s.previousSibling.data !== '[if astro]>server-island-start<![endif]')
106
- s.previousSibling.remove();
107
- s.previousSibling?.remove();
108
- // Insert the new HTML
109
- s.before(document.createRange().createContextualFragment(html));
110
- // Remove the script. Prior to v5.4.2, this was the trick to force rerun of scripts. Keeping it to minimize change to the existing behavior.
111
- s.remove();
112
- }
113
- </script>`.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("//")).join(" ")
126
+ const renderServerIslandRuntime = () => {
127
+ return `<script>${SERVER_ISLAND_REPLACER}</script>`;
128
+ };
129
+ const SERVER_ISLAND_REPLACER = markHTMLString(
130
+ `async function replaceServerIsland(id, r) {
131
+ let s = document.querySelector(\`script[data-island-id="\${id}"]\`);
132
+ // If there's no matching script, or the request fails then return
133
+ if (!s || r.status !== 200 || r.headers.get('content-type')?.split(';')[0].trim() !== 'text/html') return;
134
+ // Load the HTML before modifying the DOM in case of errors
135
+ let html = await r.text();
136
+ // Remove any placeholder content before the island script
137
+ while (s.previousSibling && s.previousSibling.nodeType !== 8 && s.previousSibling.data !== '[if astro]>server-island-start<![endif]')
138
+ s.previousSibling.remove();
139
+ s.previousSibling?.remove();
140
+ // Insert the new HTML
141
+ s.before(document.createRange().createContextualFragment(html));
142
+ // Remove the script. Prior to v5.4.2, this was the trick to force rerun of scripts. Keeping it to minimize change to the existing behavior.
143
+ s.remove();
144
+ }`.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("//")).join(" ")
114
145
  );
115
146
  export {
147
+ ServerIslandComponent,
116
148
  containsServerDirective,
117
- renderServerIsland,
118
149
  renderServerIslandRuntime
119
150
  };
@@ -75,6 +75,9 @@ Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the
75
75
  if (key === "popover" && typeof value === "boolean") {
76
76
  return markHTMLString(value ? ` popover` : "");
77
77
  }
78
+ if (key === "download" && typeof value === "boolean") {
79
+ return markHTMLString(value ? ` download` : "");
80
+ }
78
81
  return markHTMLString(` ${key}="${toAttributeString(value, shouldEscape)}"`);
79
82
  }
80
83
  function internalSpreadAttributes(values, shouldEscape = true) {
@@ -1,5 +1,5 @@
1
1
  import type { SSRResult } from '../../types/public/internal.js';
2
2
  export declare function determineIfNeedsHydrationScript(result: SSRResult): boolean;
3
3
  export declare function determinesIfNeedsDirectiveScript(result: SSRResult, directive: string): boolean;
4
- export type PrescriptType = null | 'both' | 'directive';
4
+ export type PrescriptType = 'both' | 'directive';
5
5
  export declare function getPrescripts(result: SSRResult, type: PrescriptType, directive: string): string;
@@ -1,6 +1,6 @@
1
+ import { ISLAND_STYLES } from "./astro-island-styles.js";
1
2
  import islandScriptDev from "./astro-island.prebuilt-dev.js";
2
3
  import islandScript from "./astro-island.prebuilt.js";
3
- const ISLAND_STYLES = `<style>astro-island,astro-slot,astro-static-slot{display:contents}</style>`;
4
4
  function determineIfNeedsHydrationScript(result) {
5
5
  if (result._metadata.hasHydrationScript) {
6
6
  return false;
@@ -25,13 +25,10 @@ function getDirectiveScriptText(result, directive) {
25
25
  function getPrescripts(result, type, directive) {
26
26
  switch (type) {
27
27
  case "both":
28
- return `${ISLAND_STYLES}<script>${getDirectiveScriptText(result, directive)};${process.env.NODE_ENV === "development" ? islandScriptDev : islandScript}</script>`;
28
+ return `<style>${ISLAND_STYLES}</style><script>${getDirectiveScriptText(result, directive)}</script><script>${process.env.NODE_ENV === "development" ? islandScriptDev : islandScript}</script>`;
29
29
  case "directive":
30
30
  return `<script>${getDirectiveScriptText(result, directive)}</script>`;
31
- case null:
32
- break;
33
31
  }
34
- return "";
35
32
  }
36
33
  export {
37
34
  determineIfNeedsHydrationScript,
@@ -1,7 +1,7 @@
1
1
  import type { SSRResult } from '../../types/public/internal.js';
2
2
  import type { TransitionAnimationPair, TransitionAnimationValue } from '../../types/public/view-transitions.js';
3
3
  export declare function createTransitionScope(result: SSRResult, hash: string): string;
4
- export declare function renderTransition(result: SSRResult, hash: string, animationName: TransitionAnimationValue | undefined, transitionName: string): string;
4
+ export declare function renderTransition(result: SSRResult, hash: string, animationName: TransitionAnimationValue | undefined, transitionName: string): Promise<string>;
5
5
  export declare function createAnimationScope(transitionName: string, animations: Record<string, TransitionAnimationPair>): {
6
6
  scope: string;
7
7
  styles: string;
@@ -1,4 +1,5 @@
1
1
  import cssesc from "cssesc";
2
+ import { generateCspDigest } from "../../core/encryption.js";
2
3
  import { fade, slide } from "../../transitions/index.js";
3
4
  import { markHTMLString } from "./escape.js";
4
5
  const transitionNameMap = /* @__PURE__ */ new WeakMap();
@@ -39,7 +40,7 @@ function reEncode(s) {
39
40
  }
40
41
  return reEncodeInValidStart[result.codePointAt(0) ?? 0] ? "_" + result : result;
41
42
  }
42
- function renderTransition(result, hash, animationName, transitionName) {
43
+ async function renderTransition(result, hash, animationName, transitionName) {
43
44
  if (typeof (transitionName ?? "") !== "string") {
44
45
  throw new Error(`Invalid transition name {${transitionName}}`);
45
46
  }
@@ -56,7 +57,11 @@ function renderTransition(result, hash, animationName, transitionName) {
56
57
  sheet.addAnimationRaw("new", "animation: none; mix-blend-mode: normal;");
57
58
  sheet.addModern("group", "animation: none");
58
59
  }
59
- result._metadata.extraHead.push(markHTMLString(`<style>${sheet.toString()}</style>`));
60
+ const css = sheet.toString();
61
+ if (result.shouldInjectCspMetaTags) {
62
+ result._metadata.extraStyleHashes.push(await generateCspDigest(css, result.cspAlgorithm));
63
+ }
64
+ result._metadata.extraHead.push(markHTMLString(`<style>${css}</style>`));
60
65
  return scope;
61
66
  }
62
67
  function createAnimationScope(transitionName, animations) {
@@ -9,6 +9,7 @@ import type { AssetsPrefix } from '../../core/app/types.js';
9
9
  import type { AstroConfigType } from '../../core/config/schemas/index.js';
10
10
  import type { REDIRECT_STATUS_CODES } from '../../core/constants.js';
11
11
  import type { AstroCookieSetOptions } from '../../core/cookies/cookies.js';
12
+ import type { CspAlgorithm, CspDirective, CspHash } from '../../core/csp/config.js';
12
13
  import type { LoggerLevel } from '../../core/logger/core.js';
13
14
  import type { EnvSchema } from '../../env/schema.js';
14
15
  import type { AstroIntegration } from './integrations.js';
@@ -17,6 +18,7 @@ export type Locales = (string | {
17
18
  path: string;
18
19
  })[];
19
20
  export type { AstroFontProvider as FontProvider };
21
+ export type { CspAlgorithm };
20
22
  type NormalizeLocales<T extends Locales> = {
21
23
  [K in keyof T]: T[K] extends string ? T[K] : T[K] extends {
22
24
  codes: Array<string>;
@@ -2118,6 +2120,244 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
2118
2120
  * include a trailing `-`, matching standard behavior in other Markdown tooling.
2119
2121
  */
2120
2122
  headingIdCompat?: boolean;
2123
+ /**
2124
+ * @name experimental.csp
2125
+ * @type {boolean | object}
2126
+ * @default `false`
2127
+ * @version 5.9.0
2128
+ * @description
2129
+ *
2130
+ * Enables built-in support for Content Security Policy (CSP). For more information,
2131
+ * refer to the [experimental CSP documentation](https://docs.astro.build/en/reference/experimental-flags/csp/)
2132
+ *
2133
+ */
2134
+ csp?: boolean | {
2135
+ /**
2136
+ * @name experimental.csp.algorithm
2137
+ * @type {"SHA-256" | "SHA-384" | "SHA-512"}
2138
+ * @default `'SHA-256'`
2139
+ * @version 5.9.0
2140
+ * @description
2141
+ *
2142
+ * The [hash function](https://developer.mozilla.org/en-US/docs/Glossary/Hash_function) to use to generate the hashes of the styles and scripts emitted by Astro.
2143
+ *
2144
+ * ```js
2145
+ * import { defineConfig } from 'astro/config';
2146
+ *
2147
+ * export default defineConfig({
2148
+ * experimental: {
2149
+ * csp: {
2150
+ * algorithm: 'SHA-512'
2151
+ * }
2152
+ * }
2153
+ * });
2154
+ * ```
2155
+ */
2156
+ algorithm?: CspAlgorithm;
2157
+ /**
2158
+ * @name experimental.csp.styleDirective
2159
+ * @type {{ hashes?: CspHash[], resources?: string[] }}
2160
+ * @default `undefined`
2161
+ * @version 5.9.0
2162
+ * @description
2163
+ *
2164
+ * A configuration object that allows you to override the default sources for the `style-src` directive
2165
+ * with the `resources` property, or to provide additional `hashes` to be rendered.
2166
+ *
2167
+ * These properties are added to all pages and completely override Astro's default resources, not add to them.
2168
+ * Therefore, you must explicitly specify any default values that you want to be included.
2169
+ */
2170
+ styleDirective?: {
2171
+ /**
2172
+ * @name experimental.csp.styleDirective.hashes
2173
+ * @type {CspHash[]}
2174
+ * @default `[]`
2175
+ * @version 5.9.0
2176
+ * @description
2177
+ *
2178
+ * A list of additional hashes added to the `style-src` directive.
2179
+ *
2180
+ * If you have external styles that aren't generated by Astro, this configuration option allows you to provide additional hashes to be rendered.
2181
+ *
2182
+ * You must provide hashes that start with `sha384-`, `sha512-` or `sha256-`. Other values will cause a validation error. These hashes are added to all pages.
2183
+ *
2184
+ * ```js
2185
+ * import { defineConfig } from 'astro/config';
2186
+ *
2187
+ * export default defineConfig({
2188
+ * experimental: {
2189
+ * csp: {
2190
+ * styleDirective: {
2191
+ * hashes: [
2192
+ * "sha384-styleHash",
2193
+ * "sha512-styleHash",
2194
+ * "sha256-styleHash"
2195
+ * ]
2196
+ * }
2197
+ * }
2198
+ * }
2199
+ * });
2200
+ * ```
2201
+ */
2202
+ hashes?: CspHash[];
2203
+ /**
2204
+ * @name experimental.csp.styleDirective.resources
2205
+ * @type {string[]}
2206
+ * @default `[]`
2207
+ * @version 5.9.0
2208
+ * @description
2209
+ *
2210
+ * A list of resources applied to the `style-src` directive. These resources are added to all pages and will override Astro's defaults.
2211
+ *
2212
+ * ```js
2213
+ * import { defineConfig } from 'astro/config';
2214
+ *
2215
+ * export default defineConfig({
2216
+ * experimental: {
2217
+ * csp: {
2218
+ * styleDirective: {
2219
+ * resources: [
2220
+ * "self",
2221
+ * "https://styles.cdn.example.com"
2222
+ * ]
2223
+ * }
2224
+ * }
2225
+ * }
2226
+ * });
2227
+ * ```
2228
+ */
2229
+ resources?: string[];
2230
+ };
2231
+ /**
2232
+ * @name experimental.csp.scriptDirective
2233
+ * @type {{ hashes?: CspHash[], resources?: string[], strictDynamic?: boolean }}
2234
+ * @default `undefined`
2235
+ * @version 5.9.0
2236
+ * @description
2237
+ *
2238
+ * A configuration object that allows you to override the default sources for the `script-src` directive
2239
+ * with the `resources` property, or to provide additional `hashes` to be rendered.
2240
+ *
2241
+ * These properties are added to all pages and completely override Astro's default resources, not add to them.
2242
+ * Therefore, you must explicitly specify any default values that you want to be included.
2243
+ *
2244
+ */
2245
+ scriptDirective?: {
2246
+ /**
2247
+ * @name experimental.csp.scriptDirective.hashes
2248
+ * @type {CspHash[]}
2249
+ * @default `[]`
2250
+ * @version 5.9.0
2251
+ * @description
2252
+ *
2253
+ * A list of additional hashes added to the `script-src` directive.
2254
+ *
2255
+ * If you have external scripts that aren't generated by Astro, or inline scripts, this configuration option allows you to provide additional hashes to be rendered.
2256
+ *
2257
+ * You must provide hashes that start with `sha384-`, `sha512-` or `sha256-`. Other values will cause a validation error. These hashes are added to all pages.
2258
+ *
2259
+ * ```js
2260
+ * import { defineConfig } from 'astro/config';
2261
+ *
2262
+ * export default defineConfig({
2263
+ * experimental: {
2264
+ * csp: {
2265
+ * scriptDirective: {
2266
+ * hashes: [
2267
+ * "sha384-scriptHash",
2268
+ * "sha512-scriptHash",
2269
+ * "sha256-scriptHash"
2270
+ * ]
2271
+ * }
2272
+ * }
2273
+ * }
2274
+ * });
2275
+ * ```
2276
+ */
2277
+ hashes?: CspHash[];
2278
+ /**
2279
+ * @name experimental.csp.scriptDirective.resources
2280
+ * @type {string[]}
2281
+ * @default `[]`
2282
+ * @version 5.9.0
2283
+ * @description
2284
+ *
2285
+ * A list of resources applied to the `script-src` directive. These resources are added to all pages and will override Astro's defaults.
2286
+ *
2287
+ * ```js
2288
+ * import { defineConfig } from 'astro/config';
2289
+ *
2290
+ * export default defineConfig({
2291
+ * experimental: {
2292
+ * csp: {
2293
+ * scriptDirective: {
2294
+ * resources: [
2295
+ * "self",
2296
+ * "https://cdn.example.com"
2297
+ * ]
2298
+ * }
2299
+ * }
2300
+ * }
2301
+ * });
2302
+ * ```
2303
+ *
2304
+ */
2305
+ resources?: string[];
2306
+ /**
2307
+ * @name experimental.csp.scriptDirective.strictDynamic
2308
+ * @type {boolean}
2309
+ * @default `false`
2310
+ * @version 5.9.0
2311
+ * @description
2312
+ *
2313
+ * Enables the keyword `strict-dynamic` to support the dynamic injection of scripts.
2314
+ *
2315
+ * ```js
2316
+ * import { defineConfig } from 'astro/config';
2317
+ *
2318
+ * export default defineConfig({
2319
+ * experimental: {
2320
+ * csp: {
2321
+ * scriptDirective: {
2322
+ * strictDynamic: true
2323
+ * }
2324
+ * }
2325
+ * }
2326
+ * });
2327
+ * ```
2328
+ */
2329
+ strictDynamic?: boolean;
2330
+ };
2331
+ /**
2332
+ * @name experimental.csp.directives
2333
+ * @type {string[]}
2334
+ * @default `[]`
2335
+ * @version 5.9.0
2336
+ * @description
2337
+ *
2338
+ * An array of additional directives to add the content of the `Content-Security-Policy` `<meta>` element.
2339
+ *
2340
+ * Use this configuration to add other directive definitions such as `default-src`, `image-src`, etc.
2341
+ *
2342
+ * ##### Example
2343
+ *
2344
+ * You can define a directive to fetch images only from a CDN `cdn.example.com`.
2345
+ *
2346
+ * ```js
2347
+ * export default defineConfig({
2348
+ * experimental: {
2349
+ * csp: {
2350
+ * directives: [
2351
+ * "image-src 'https://cdn.example.com"
2352
+ * ]
2353
+ * }
2354
+ * }
2355
+ * })
2356
+ * ```
2357
+ *
2358
+ */
2359
+ directives?: CspDirective[];
2360
+ };
2121
2361
  /**
2122
2362
  * @name experimental.preserveScriptOrder
2123
2363
  * @type {boolean}
@@ -2,6 +2,7 @@ import type { z } from 'zod';
2
2
  import type { ActionAccept, ActionClient, ActionReturnType } from '../../actions/runtime/virtual/server.js';
3
3
  import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../core/constants.js';
4
4
  import type { AstroCookies } from '../../core/cookies/cookies.js';
5
+ import type { CspDirective, CspHash } from '../../core/csp/config.js';
5
6
  import type { AstroSession } from '../../core/session.js';
6
7
  import type { AstroComponentFactory } from '../../runtime/server/index.js';
7
8
  import type { Params, RewritePayload } from './common.js';
@@ -310,6 +311,56 @@ export interface AstroSharedContext<Props extends Record<string, any> = Record<s
310
311
  * Whether the current route is prerendered or not.
311
312
  */
312
313
  isPrerendered: boolean;
314
+ /**
315
+ * It adds a specific CSP directive to the route being rendered.
316
+ *
317
+ * ## Example
318
+ *
319
+ * ```js
320
+ * ctx.insertDirective("default-src 'self' 'unsafe-inline' https://example.com")
321
+ * ```
322
+ */
323
+ insertDirective: (directive: CspDirective) => void;
324
+ /**
325
+ * It set the resource for the directive `style-src` in the route being rendered. It overrides Astro's default.
326
+ *
327
+ * ## Example
328
+ *
329
+ * ```js
330
+ * ctx.insertStyleResource("https://styles.cdn.example.com/")
331
+ * ```
332
+ */
333
+ insertStyleResource: (payload: string) => void;
334
+ /**
335
+ * Insert a single style hash to the route being rendered.
336
+ *
337
+ * ## Example
338
+ *
339
+ * ```js
340
+ * ctx.insertStyleHash("sha256-1234567890abcdef1234567890")
341
+ * ```
342
+ */
343
+ insertStyleHash: (hash: CspHash) => void;
344
+ /**
345
+ * It set the resource for the directive `script-src` in the route being rendered.
346
+ *
347
+ * ## Example
348
+ *
349
+ * ```js
350
+ * ctx.insertScriptResource("https://scripts.cdn.example.com/")
351
+ * ```
352
+ */
353
+ insertScriptResource: (resource: string) => void;
354
+ /**
355
+ * Insert a single script hash to the route being rendered.
356
+ *
357
+ * ## Example
358
+ *
359
+ * ```js
360
+ * ctx.insertScriptHash("sha256-1234567890abcdef1234567890")
361
+ * ```
362
+ */
363
+ insertScriptHash: (hash: CspHash) => void;
313
364
  }
314
365
  /**
315
366
  * The `APIContext` is the object made available to endpoints and middleware.
@@ -53,6 +53,15 @@ export type AdapterSupportsKind = (typeof AdapterFeatureStability)[keyof typeof
53
53
  export type AdapterSupportWithMessage = {
54
54
  support: Exclude<AdapterSupportsKind, 'stable'>;
55
55
  message: string;
56
+ /**
57
+ * Determines if a feature support warning/error in the adapter should be suppressed:
58
+ * - `"default"`: Suppresses the default warning/error message.
59
+ * - `"all"`: Suppresses both the custom and the default warning/error message.
60
+ *
61
+ * This is useful when the warning/error might not be applicable in certain contexts,
62
+ * or the default message might cause confusion and conflict with a custom one.
63
+ */
64
+ suppress?: 'all' | 'default';
56
65
  };
57
66
  export type AdapterSupport = AdapterSupportsKind | AdapterSupportWithMessage;
58
67
  export interface AstroAdapterFeatures {