astro 7.0.9 → 7.1.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 (69) hide show
  1. package/dist/assets/fonts/infra/fs-font-file-content-resolver.js +1 -1
  2. package/dist/cli/dev/index.d.ts +17 -0
  3. package/dist/cli/dev/index.js +56 -2
  4. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  5. package/dist/content/consts.d.ts +2 -0
  6. package/dist/content/consts.js +4 -0
  7. package/dist/content/content-layer.d.ts +0 -6
  8. package/dist/content/content-layer.js +4 -9
  9. package/dist/content/data-store-source.d.ts +36 -0
  10. package/dist/content/data-store-source.js +30 -0
  11. package/dist/content/data-store-writer.d.ts +67 -0
  12. package/dist/content/data-store-writer.js +94 -0
  13. package/dist/content/data-store.d.ts +15 -0
  14. package/dist/content/data-store.js +33 -3
  15. package/dist/content/loaders/glob.d.ts +7 -0
  16. package/dist/content/loaders/glob.js +2 -2
  17. package/dist/content/mutable-data-store.d.ts +8 -0
  18. package/dist/content/mutable-data-store.js +52 -15
  19. package/dist/content/paths.d.ts +14 -0
  20. package/dist/content/paths.js +11 -0
  21. package/dist/content/runtime.js +4 -4
  22. package/dist/content/vite-plugin-content-virtual-mod.js +29 -2
  23. package/dist/core/app/dev/pipeline.js +2 -1
  24. package/dist/core/app/types.d.ts +39 -5
  25. package/dist/core/base-pipeline.d.ts +1 -1
  26. package/dist/core/base-pipeline.js +6 -2
  27. package/dist/core/build/index.js +2 -1
  28. package/dist/core/build/plugins/plugin-manifest.js +23 -8
  29. package/dist/core/config/schemas/base.d.ts +38 -5
  30. package/dist/core/config/schemas/base.js +15 -8
  31. package/dist/core/config/schemas/relative.d.ts +73 -15
  32. package/dist/core/constants.js +1 -1
  33. package/dist/core/csp/common.d.ts +7 -7
  34. package/dist/core/csp/common.js +8 -9
  35. package/dist/core/csp/config.d.ts +45 -0
  36. package/dist/core/csp/config.js +44 -2
  37. package/dist/core/csp/runtime.d.ts +21 -1
  38. package/dist/core/csp/runtime.js +31 -0
  39. package/dist/core/dev/dev.js +9 -4
  40. package/dist/core/dev/restart.js +7 -1
  41. package/dist/core/errors/zod-error-map.js +7 -1
  42. package/dist/core/fetch/fetch-state.js +52 -10
  43. package/dist/core/logger/config.d.ts +1 -1
  44. package/dist/core/logger/core.d.ts +4 -4
  45. package/dist/core/logger/impls/console.d.ts +2 -2
  46. package/dist/core/logger/impls/json.d.ts +2 -2
  47. package/dist/core/logger/impls/json.js +3 -14
  48. package/dist/core/logger/impls/node.d.ts +2 -2
  49. package/dist/core/logger/load.d.ts +2 -2
  50. package/dist/core/logger/load.js +19 -27
  51. package/dist/core/messages/runtime.d.ts +8 -0
  52. package/dist/core/messages/runtime.js +34 -1
  53. package/dist/core/render/paginate.js +21 -16
  54. package/dist/core/sync/index.js +23 -8
  55. package/dist/integrations/hooks.js +8 -0
  56. package/dist/manifest/serialized.js +21 -6
  57. package/dist/runtime/server/escape.d.ts +2 -0
  58. package/dist/runtime/server/escape.js +4 -0
  59. package/dist/runtime/server/render/csp.js +80 -19
  60. package/dist/runtime/server/render/server-islands.js +6 -4
  61. package/dist/runtime/server/transition.js +2 -2
  62. package/dist/types/astro.d.ts +2 -3
  63. package/dist/types/public/common.d.ts +13 -0
  64. package/dist/types/public/config.d.ts +196 -15
  65. package/dist/types/public/context.d.ts +27 -11
  66. package/dist/types/public/internal.d.ts +12 -11
  67. package/dist/vite-plugin-app/pipeline.js +2 -1
  68. package/dist/vite-plugin-astro-server/route-guard.js +3 -2
  69. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { AstroLogger, type AstroLoggerDestination, type AstroLoggerLevel, type AstroLoggerMessage } from '../core.js';
1
+ import { AstroLogger, type AstroLoggerDestination, type AstroLoggerLevel } from '../core.js';
2
2
  import type { AstroInlineConfig } from '../../../types/public/index.js';
3
3
  export type JsonHandlerConfig = {
4
4
  /**
@@ -11,5 +11,5 @@ export type JsonHandlerConfig = {
11
11
  level?: AstroLoggerLevel;
12
12
  };
13
13
  export declare const SGR_REGEX: RegExp;
14
- export default function jsonLoggerDestination(config?: JsonHandlerConfig): AstroLoggerDestination<AstroLoggerMessage>;
14
+ export default function jsonLoggerDestination(config?: JsonHandlerConfig): AstroLoggerDestination;
15
15
  export declare function createJsonLoggerFromFlags(config: AstroInlineConfig): AstroLogger;
@@ -8,24 +8,13 @@ function jsonLoggerDestination(config = {}) {
8
8
  const { pretty = false, level = "info" } = config;
9
9
  return {
10
10
  write(event) {
11
- let dest = process.stderr;
12
- if (levels[event.level] < levels["error"]) {
13
- dest = process.stdout;
14
- }
15
11
  if (!matchesLevel(event.level, level)) {
16
12
  return;
17
13
  }
18
- let trailingLine = event.newLine ? "\n" : "";
14
+ const dest = levels[event.level] >= levels["error"] ? console.error : console.info;
19
15
  const message = event.message.replace(SGR_REGEX, "");
20
- if (pretty) {
21
- dest.write(
22
- JSON.stringify({ message, label: event.label, level: event.level }, null, 2) + trailingLine
23
- );
24
- } else {
25
- dest.write(
26
- JSON.stringify({ message, label: event.label, level: event.level }) + trailingLine
27
- );
28
- }
16
+ const payload = pretty ? JSON.stringify({ message, label: event.label, level: event.level }, null, 2) : JSON.stringify({ message, label: event.label, level: event.level });
17
+ dest(payload);
29
18
  }
30
19
  };
31
20
  }
@@ -1,7 +1,7 @@
1
- import { AstroLogger, type AstroLoggerDestination, type AstroLoggerLevel, type AstroLoggerMessage } from '../core.js';
1
+ import { AstroLogger, type AstroLoggerDestination, type AstroLoggerLevel } from '../core.js';
2
2
  import type { AstroInlineConfig } from '../../../types/public/index.js';
3
3
  export type NodeHandlerConfig = {
4
4
  level?: AstroLoggerLevel;
5
5
  };
6
- export default function (options?: NodeHandlerConfig): AstroLoggerDestination<AstroLoggerMessage>;
6
+ export default function (options?: NodeHandlerConfig): AstroLoggerDestination;
7
7
  export declare function createNodeLoggerFromFlags(inlineConfig: AstroInlineConfig): AstroLogger;
@@ -1,7 +1,7 @@
1
- import { AstroLogger, type AstroLoggerLevel } from './core.js';
1
+ import { AstroLogger, type AstroLoggerDestination } from './core.js';
2
2
  import type { LoggerHandlerConfig } from './config.js';
3
3
  import type { AstroConfig, AstroInlineConfig } from '../../types/public/index.js';
4
- export declare function loadLogger(config: LoggerHandlerConfig, level?: AstroLoggerLevel): Promise<AstroLogger>;
4
+ export declare function loadLoggerDestination(config: LoggerHandlerConfig): Promise<AstroLoggerDestination>;
5
5
  /**
6
6
  * It attempts to load a logger from the entrypoint.
7
7
  * If not provided, it creates a new logger instance on the fly.
@@ -5,27 +5,22 @@ import { default as nodeLoggerCreator, createNodeLoggerFromFlags } from "./impls
5
5
  import { default as consoleLoggerCreator } from "./impls/console.js";
6
6
  import { default as jsonLoggerCreator } from "./impls/json.js";
7
7
  import { default as composeLoggerCreator } from "./impls/compose.js";
8
- async function loadLogger(config, level = "info") {
8
+ function normalizeEntrypoint(entrypoint) {
9
+ return entrypoint instanceof URL ? entrypoint.href : entrypoint;
10
+ }
11
+ async function loadLoggerDestination(config) {
9
12
  let cause = void 0;
13
+ const entrypoint = normalizeEntrypoint(config.entrypoint);
10
14
  try {
11
15
  switch (config.entrypoint) {
12
16
  case "astro/logger/node": {
13
- return new AstroLogger({
14
- destination: nodeLoggerCreator(config.config),
15
- level
16
- });
17
+ return nodeLoggerCreator(config.config);
17
18
  }
18
19
  case "astro/logger/console": {
19
- return new AstroLogger({
20
- destination: consoleLoggerCreator(config.config),
21
- level
22
- });
20
+ return consoleLoggerCreator(config.config);
23
21
  }
24
22
  case "astro/logger/json": {
25
- return new AstroLogger({
26
- destination: jsonLoggerCreator(config.config),
27
- level
28
- });
23
+ return jsonLoggerCreator(config.config);
29
24
  }
30
25
  case "astro/logger/compose": {
31
26
  let destinations = [];
@@ -35,26 +30,20 @@ async function loadLogger(config, level = "info") {
35
30
  loggers.map(async (loggerConfig) => {
36
31
  const logger = await import(
37
32
  /* @vite-ignore */
38
- loggerConfig.entrypoint
33
+ normalizeEntrypoint(loggerConfig.entrypoint)
39
34
  );
40
35
  return logger.default(loggerConfig.config);
41
36
  })
42
37
  );
43
38
  }
44
- return new AstroLogger({
45
- destination: composeLoggerCreator(destinations),
46
- level
47
- });
39
+ return composeLoggerCreator(destinations);
48
40
  }
49
41
  default: {
50
- const nodeLogger = await import(
42
+ const logger = await import(
51
43
  /* @vite-ignore */
52
- config.entrypoint
44
+ entrypoint
53
45
  );
54
- return new AstroLogger({
55
- destination: nodeLogger.default(config.config),
56
- level
57
- });
46
+ return logger.default(config.config);
58
47
  }
59
48
  }
60
49
  } catch (e) {
@@ -64,7 +53,7 @@ async function loadLogger(config, level = "info") {
64
53
  }
65
54
  const error = new AstroError({
66
55
  ...UnableToLoadLogger,
67
- message: UnableToLoadLogger.message(config.entrypoint)
56
+ message: UnableToLoadLogger.message(entrypoint)
68
57
  });
69
58
  if (cause) {
70
59
  error.cause = cause;
@@ -75,7 +64,10 @@ async function loadOrCreateNodeLogger(astroConfig, inlineAstroConfig) {
75
64
  if (inlineAstroConfig._logger) return inlineAstroConfig._logger;
76
65
  try {
77
66
  if (astroConfig.logger) {
78
- return await loadLogger(astroConfig.logger, inlineAstroConfig.logLevel);
67
+ return new AstroLogger({
68
+ destination: await loadLoggerDestination(astroConfig.logger),
69
+ level: inlineAstroConfig.logLevel ?? "info"
70
+ });
79
71
  } else {
80
72
  return createNodeLoggerFromFlags(inlineAstroConfig);
81
73
  }
@@ -84,6 +76,6 @@ async function loadOrCreateNodeLogger(astroConfig, inlineAstroConfig) {
84
76
  }
85
77
  }
86
78
  export {
87
- loadLogger,
79
+ loadLoggerDestination,
88
80
  loadOrCreateNodeLogger
89
81
  };
@@ -58,3 +58,11 @@ export declare function printHelp({ commandName, headline, usage, tables, descri
58
58
  description?: string;
59
59
  }): void;
60
60
  export declare function warnIfCspWithShiki(config: AstroConfig, logger: AstroLogger): void;
61
+ /**
62
+ * Warns when a `scriptDirective`/`styleDirective` defines `default`-kind resources alongside
63
+ * `element`/`attribute`-kind entries. Because the more specific directive (`*-src-elem`/`*-src-attr`)
64
+ * overrides the generic one for its scope and browsers do not fall back, the generic resources will
65
+ * not apply there. Astro's generated hashes are folded automatically, so this only concerns
66
+ * user-provided resources.
67
+ */
68
+ export declare function warnIfCspResourceFallbackShadowing(config: AstroConfig, logger: AstroLogger): void;
@@ -1,4 +1,5 @@
1
1
  import colors from "piccolore";
2
+ import { partitionByKind } from "../csp/runtime.js";
2
3
  import { getDocsForError, renderErrorMarkdown } from "../errors/dev/runtime.js";
3
4
  import {
4
5
  AstroError,
@@ -269,7 +270,7 @@ function printHelp({
269
270
  message.push(
270
271
  linebreak(),
271
272
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"7.0.9"}`
273
+ `v${"7.1.0"}`
273
274
  )} ${headline}`
274
275
  );
275
276
  }
@@ -304,6 +305,37 @@ function warnIfCspWithShiki(config, logger) {
304
305
  );
305
306
  }
306
307
  }
308
+ function warnIfCspResourceFallbackShadowing(config, logger) {
309
+ const csp = config.security.csp;
310
+ if (typeof csp !== "object") return;
311
+ const families = [
312
+ { name: "script", directive: csp.scriptDirective },
313
+ { name: "style", directive: csp.styleDirective }
314
+ ];
315
+ for (const { name, directive } of families) {
316
+ const sources = partitionByKind({
317
+ resources: directive?.resources ?? [],
318
+ hashes: directive?.hashes ?? []
319
+ });
320
+ if (sources.default.resources.length === 0) continue;
321
+ const shadowed = [];
322
+ if (sources.element.resources.length > 0 || sources.element.hashes.length > 0) {
323
+ shadowed.push(`\`${name}-src-elem\``);
324
+ }
325
+ if (sources.attribute.resources.length > 0 || sources.attribute.hashes.length > 0) {
326
+ shadowed.push(`\`${name}-src-attr\``);
327
+ }
328
+ if (shadowed.length === 0) continue;
329
+ logger.warn(
330
+ "csp",
331
+ `\`security.csp.${name}Directive\` defines \`${name}-src\` resources (${sources.default.resources.join(
332
+ " "
333
+ )}) as well as ${shadowed.join(" and ")} resources or hashes. Because ${shadowed.join(
334
+ " and "
335
+ )} override \`${name}-src\` for their scope (browsers do not fall back), those \`${name}-src\` resources will not apply there. Add them to the corresponding \`kind\` if needed.`
336
+ );
337
+ }
338
+ }
307
339
  export {
308
340
  actionRequired,
309
341
  cancelled,
@@ -327,5 +359,6 @@ export {
327
359
  telemetryEnabled,
328
360
  telemetryNotice,
329
361
  telemetryReset,
362
+ warnIfCspResourceFallbackShadowing,
330
363
  warnIfCspWithShiki
331
364
  };
@@ -4,11 +4,12 @@ import { getRouteGenerator } from "../routing/generator.js";
4
4
  function generatePaginateFunction(routeMatch, base, trailingSlash) {
5
5
  return function paginateUtility(data, args = {}) {
6
6
  const generate = getRouteGenerator(routeMatch.segments, trailingSlash);
7
- let { pageSize: _pageSize, params: _params, props: _props } = args;
7
+ let { pageSize: _pageSize, params: _params, props: _props, format: _format } = args;
8
8
  const pageSize = _pageSize || 10;
9
9
  const paramName = "page";
10
10
  const additionalParams = _params || {};
11
11
  const additionalProps = _props || {};
12
+ const formatUrl = _format || ((url) => url);
12
13
  let includesFirstPageNumber;
13
14
  if (routeMatch.params.includes(`...${paramName}`)) {
14
15
  includesFirstPageNumber = false;
@@ -29,23 +30,27 @@ function generatePaginateFunction(routeMatch, base, trailingSlash) {
29
30
  ...additionalParams,
30
31
  [paramName]: includesFirstPageNumber || pageNum > 1 ? String(pageNum) : void 0
31
32
  };
32
- const current = addRouteBase(generate({ ...params }), base);
33
- const next = pageNum === lastPage ? void 0 : addRouteBase(generate({ ...params, page: String(pageNum + 1) }), base);
34
- const prev = pageNum === 1 ? void 0 : addRouteBase(
35
- generate({
36
- ...params,
37
- page: !includesFirstPageNumber && pageNum - 1 === 1 ? void 0 : String(pageNum - 1)
38
- }),
39
- base
33
+ const current = formatUrl(addRouteBase(generate({ ...params }), base));
34
+ const next = pageNum === lastPage ? void 0 : formatUrl(addRouteBase(generate({ ...params, page: String(pageNum + 1) }), base));
35
+ const prev = pageNum === 1 ? void 0 : formatUrl(
36
+ addRouteBase(
37
+ generate({
38
+ ...params,
39
+ page: !includesFirstPageNumber && pageNum - 1 === 1 ? void 0 : String(pageNum - 1)
40
+ }),
41
+ base
42
+ )
40
43
  );
41
- const first = pageNum === 1 ? void 0 : addRouteBase(
42
- generate({
43
- ...params,
44
- page: includesFirstPageNumber ? "1" : void 0
45
- }),
46
- base
44
+ const first = pageNum === 1 ? void 0 : formatUrl(
45
+ addRouteBase(
46
+ generate({
47
+ ...params,
48
+ page: includesFirstPageNumber ? "1" : void 0
49
+ }),
50
+ base
51
+ )
47
52
  );
48
- const last = pageNum === lastPage ? void 0 : addRouteBase(generate({ ...params, page: String(lastPage) }), base);
53
+ const last = pageNum === lastPage ? void 0 : formatUrl(addRouteBase(generate({ ...params, page: String(lastPage) }), base));
49
54
  return {
50
55
  params,
51
56
  props: {
@@ -6,7 +6,7 @@ import colors from "piccolore";
6
6
  import { createServer } from "vite";
7
7
  import { syncFonts } from "../../assets/fonts/sync.js";
8
8
  import { CONTENT_TYPES_FILE } from "../../content/consts.js";
9
- import { getDataStoreFile } from "../../content/content-layer.js";
9
+ import { getDataStoreDir, getDataStoreFile } from "../../content/paths.js";
10
10
  import { globalContentLayer } from "../../content/instance.js";
11
11
  import { createContentTypesGenerator } from "../../content/index.js";
12
12
  import { MutableDataStore } from "../../content/mutable-data-store.js";
@@ -59,11 +59,21 @@ async function clearContentLayerCache({
59
59
  fs = fsMod,
60
60
  isDev
61
61
  }) {
62
- const dataStore = getDataStoreFile(settings, isDev);
63
- if (fs.existsSync(dataStore)) {
64
- logger.debug("content", "clearing data store");
65
- await fs.promises.rm(dataStore, { force: true });
66
- logger.warn("content", "data store cleared (force)");
62
+ if (settings.config.experimental.collectionStorage === "chunked") {
63
+ const dataStore = getDataStoreDir(settings, isDev);
64
+ if (fs.existsSync(dataStore)) {
65
+ logger.debug("content", "clearing data store");
66
+ await fs.promises.rm(dataStore, { force: true, recursive: true });
67
+ await fs.promises.mkdir(dataStore, { recursive: true });
68
+ logger.warn("content", "data store cleared (force)");
69
+ }
70
+ } else {
71
+ const dataStore = getDataStoreFile(settings, isDev);
72
+ if (fs.existsSync(dataStore)) {
73
+ logger.debug("content", "clearing data store");
74
+ await fs.promises.rm(dataStore, { force: true });
75
+ logger.warn("content", "data store cleared (force)");
76
+ }
67
77
  }
68
78
  }
69
79
  async function syncInternal({
@@ -88,8 +98,13 @@ async function syncInternal({
88
98
  settings.timer.start("Sync content layer");
89
99
  let store;
90
100
  try {
91
- const dataStoreFile = getDataStoreFile(settings, isDev);
92
- store = await MutableDataStore.fromFile(dataStoreFile);
101
+ if (settings.config.experimental.collectionStorage === "chunked") {
102
+ const dataStoreDir = getDataStoreDir(settings, isDev);
103
+ store = await MutableDataStore.fromDir(dataStoreDir);
104
+ } else {
105
+ const dataStoreFile = getDataStoreFile(settings, isDev);
106
+ store = await MutableDataStore.fromFile(dataStoreFile);
107
+ }
93
108
  } catch (err) {
94
109
  logger.error("content", err.message);
95
110
  }
@@ -14,6 +14,7 @@ import { validateSetAdapter } from "../core/dev/adapter-validation.js";
14
14
  import { getRouteGenerator } from "../core/routing/generator.js";
15
15
  import { getClientOutputDirectory } from "../prerender/utils.js";
16
16
  import { validateSupportedFeatures } from "./features-validation.js";
17
+ import { loadLoggerDestination } from "../core/logger/load.js";
17
18
  async function withTakingALongTimeMsg({
18
19
  name,
19
20
  hookName,
@@ -139,6 +140,7 @@ async function runHookConfigSetup({
139
140
  let astroJSXRenderer = null;
140
141
  for (let i = 0; i < updatedConfig.integrations.length; i++) {
141
142
  const integration = updatedConfig.integrations[i];
143
+ let isLoggerUpdated = false;
142
144
  const { integrationLogger } = await runHookInternal({
143
145
  integration,
144
146
  hookName: "astro:config:setup",
@@ -169,6 +171,9 @@ async function runHookConfigSetup({
169
171
  updatedSettings.scripts.push({ stage, content });
170
172
  },
171
173
  updateConfig: (newConfig) => {
174
+ if (newConfig.logger?.entrypoint) {
175
+ isLoggerUpdated = true;
176
+ }
172
177
  updatedConfig = mergeConfig(updatedConfig, newConfig);
173
178
  return { ...updatedConfig };
174
179
  },
@@ -254,6 +259,9 @@ async function runHookConfigSetup({
254
259
  }
255
260
  try {
256
261
  updatedConfig = await validateConfigRefined(updatedConfig);
262
+ if (isLoggerUpdated) {
263
+ logger.setDestination(await loadLoggerDestination(updatedConfig.logger));
264
+ }
257
265
  } catch (error) {
258
266
  integrationLogger.error("An error occurred while updating the config");
259
267
  throw error;
@@ -14,6 +14,7 @@ import {
14
14
  getStyleResources,
15
15
  shouldTrackCspHashes
16
16
  } from "../core/csp/common.js";
17
+ import { partitionByKind } from "../core/csp/runtime.js";
17
18
  import { createKey, encodeKey, getEnvironmentKey, hasEnvironmentKey } from "../core/encryption.js";
18
19
  import { MIDDLEWARE_MODULE_ID } from "../core/middleware/vite-plugin.js";
19
20
  import { SERVER_ISLAND_MANIFEST } from "../core/server-islands/vite-plugin-server-islands.js";
@@ -131,15 +132,29 @@ async function createSerializedManifest(settings, encodedKey) {
131
132
  };
132
133
  }
133
134
  if (shouldTrackCspHashes(settings.config.security.csp)) {
135
+ const cspConfig = settings.config.security.csp;
136
+ const scriptDirective = {
137
+ resources: getScriptResources(cspConfig),
138
+ hashes: getScriptHashes(cspConfig),
139
+ strictDynamic: getStrictDynamic(cspConfig)
140
+ };
141
+ const styleDirective = {
142
+ resources: getStyleResources(cspConfig),
143
+ hashes: getStyleHashes(cspConfig)
144
+ };
145
+ const scriptDefault = partitionByKind(scriptDirective).default;
146
+ const styleDefault = partitionByKind(styleDirective).default;
134
147
  csp = {
135
148
  cspDestination: settings.adapter?.adapterFeatures?.staticHeaders ? "adapter" : void 0,
136
- scriptHashes: getScriptHashes(settings.config.security.csp),
137
- scriptResources: getScriptResources(settings.config.security.csp),
138
- styleHashes: getStyleHashes(settings.config.security.csp),
139
- styleResources: getStyleResources(settings.config.security.csp),
140
- algorithm: getAlgorithm(settings.config.security.csp),
149
+ algorithm: getAlgorithm(cspConfig),
141
150
  directives: getDirectives(settings),
142
- isStrictDynamic: getStrictDynamic(settings.config.security.csp)
151
+ scriptHashes: scriptDefault.hashes,
152
+ scriptResources: scriptDefault.resources,
153
+ isStrictDynamic: scriptDirective.strictDynamic,
154
+ styleHashes: styleDefault.hashes,
155
+ styleResources: styleDefault.resources,
156
+ scriptDirective,
157
+ styleDirective
143
158
  };
144
159
  }
145
160
  let loggerConfig = void 0;
@@ -7,6 +7,8 @@ export declare const escapeHTML: (str: string) => string;
7
7
  * @see https://mathiasbynens.be/notes/etago
8
8
  */
9
9
  export declare function stringifyForScript(value: any): string;
10
+ /** Escapes CSS text so it can be embedded inside a `<style>` tag. */
11
+ export declare function escapeStyleText(value: string): string;
10
12
  export declare class HTMLBytes extends Uint8Array {
11
13
  }
12
14
  declare const htmlStringSymbol: unique symbol;
@@ -4,6 +4,9 @@ const escapeHTML = escape;
4
4
  function stringifyForScript(value) {
5
5
  return JSON.stringify(value)?.replace(/</g, "\\u003c");
6
6
  }
7
+ function escapeStyleText(value) {
8
+ return value.replaceAll("<", "\\3C ");
9
+ }
7
10
  class HTMLBytes extends Uint8Array {
8
11
  }
9
12
  Object.defineProperty(HTMLBytes.prototype, Symbol.toStringTag, {
@@ -77,6 +80,7 @@ export {
77
80
  HTMLBytes,
78
81
  HTMLString,
79
82
  escapeHTML,
83
+ escapeStyleText,
80
84
  isHTMLBytes,
81
85
  isHTMLString,
82
86
  markHTMLString,
@@ -1,34 +1,95 @@
1
+ import { partitionByKind } from "../../../core/csp/runtime.js";
1
2
  function renderCspContent(result) {
3
+ const { scriptDirective, styleDirective, directives } = result;
4
+ const script = partitionByKind(scriptDirective);
5
+ const style = partitionByKind(styleDirective);
2
6
  const finalScriptHashes = /* @__PURE__ */ new Set();
3
- const finalStyleHashes = /* @__PURE__ */ new Set();
4
- for (const scriptHash of result.scriptHashes) {
7
+ for (const scriptHash of script.default.hashes) {
5
8
  finalScriptHashes.add(`'${scriptHash}'`);
6
9
  }
7
- for (const styleHash of result.styleHashes) {
10
+ for (const scriptHash of result._metadata.extraScriptHashes) {
11
+ finalScriptHashes.add(`'${scriptHash}'`);
12
+ }
13
+ const finalStyleHashes = /* @__PURE__ */ new Set();
14
+ for (const styleHash of style.default.hashes) {
8
15
  finalStyleHashes.add(`'${styleHash}'`);
9
16
  }
10
17
  for (const styleHash of result._metadata.extraStyleHashes) {
11
18
  finalStyleHashes.add(`'${styleHash}'`);
12
19
  }
13
- for (const scriptHash of result._metadata.extraScriptHashes) {
14
- finalScriptHashes.add(`'${scriptHash}'`);
15
- }
16
- let directives;
17
- if (result.directives.length > 0) {
18
- directives = result.directives.join(";") + ";";
20
+ let directivesContent;
21
+ if (directives.length > 0) {
22
+ directivesContent = directives.join(";") + ";";
19
23
  }
20
- let scriptResources = "'self'";
21
- if (result.scriptResources.length > 0) {
22
- scriptResources = result.scriptResources.map((r) => `${r}`).join(" ");
24
+ const scriptResources = script.default.resources.length > 0 ? script.default.resources.join(" ") : "'self'";
25
+ const styleResources = style.default.resources.length > 0 ? style.default.resources.join(" ") : "'self'";
26
+ const scriptElementDefaultResource = script.default.resources.length > 0 ? "" : "'self'";
27
+ const styleElementDefaultResource = style.default.resources.length > 0 ? "" : "'self'";
28
+ const scriptElemActive = isEnabled(script.element);
29
+ const styleElemActive = isEnabled(style.element);
30
+ const strictDynamicSuffix = scriptDirective.strictDynamic ? ` 'strict-dynamic'` : "";
31
+ const scriptBaselineTokens = [
32
+ ...scriptElemActive ? [] : [...finalScriptHashes],
33
+ ...scriptDirective.strictDynamic ? [`'strict-dynamic'`] : []
34
+ ];
35
+ const scriptSrc = `script-src ${scriptResources} ${scriptBaselineTokens.join(" ")};`;
36
+ const styleSrc = `style-src ${styleResources} ${(styleElemActive ? [] : [...finalStyleHashes]).join(" ")};`;
37
+ const scriptSrcElem = scriptElemActive ? renderSpecificDirective(
38
+ "script-src-elem",
39
+ script.element.resources,
40
+ scriptElementDefaultResource,
41
+ finalScriptHashes,
42
+ script.element.hashes,
43
+ strictDynamicSuffix
44
+ ) : void 0;
45
+ const scriptSrcAttr = isEnabled(script.attribute) ? renderSpecificDirective(
46
+ "script-src-attr",
47
+ script.attribute.resources,
48
+ "'none'",
49
+ void 0,
50
+ script.attribute.hashes
51
+ ) : void 0;
52
+ const styleSrcElem = styleElemActive ? renderSpecificDirective(
53
+ "style-src-elem",
54
+ style.element.resources,
55
+ styleElementDefaultResource,
56
+ finalStyleHashes,
57
+ style.element.hashes
58
+ ) : void 0;
59
+ const styleSrcAttr = isEnabled(style.attribute) ? renderSpecificDirective(
60
+ "style-src-attr",
61
+ style.attribute.resources,
62
+ "'none'",
63
+ void 0,
64
+ style.attribute.hashes
65
+ ) : void 0;
66
+ return [
67
+ directivesContent,
68
+ scriptSrc,
69
+ scriptSrcElem,
70
+ scriptSrcAttr,
71
+ styleSrc,
72
+ styleSrcElem,
73
+ styleSrcAttr
74
+ ].filter(Boolean).join(" ");
75
+ }
76
+ function isEnabled(sources) {
77
+ return sources.resources.length > 0 || sources.hashes.length > 0;
78
+ }
79
+ function renderSpecificDirective(name, resources, defaultResource, sharedHashes, ownHashes, suffix = "") {
80
+ const hashes = new Set(sharedHashes);
81
+ for (const hash of ownHashes) {
82
+ hashes.add(`'${hash}'`);
23
83
  }
24
- let styleResources = "'self'";
25
- if (result.styleResources.length > 0) {
26
- styleResources = result.styleResources.map((r) => `${r}`).join(" ");
84
+ let finalResources;
85
+ if (resources.length > 0) {
86
+ finalResources = resources.map((r) => `${r}`).join(" ");
87
+ } else if (defaultResource === "'none'" && hashes.size > 0) {
88
+ finalResources = "";
89
+ } else {
90
+ finalResources = defaultResource;
27
91
  }
28
- const strictDynamic = result.isStrictDynamic ? ` 'strict-dynamic'` : "";
29
- const scriptSrc = `script-src ${scriptResources} ${Array.from(finalScriptHashes).join(" ")}${strictDynamic};`;
30
- const styleSrc = `style-src ${styleResources} ${Array.from(finalStyleHashes).join(" ")};`;
31
- return [directives, scriptSrc, styleSrc].filter(Boolean).join(" ");
92
+ return `${name} ${[finalResources, ...hashes].filter(Boolean).join(" ")}${suffix};`;
32
93
  }
33
94
  export {
34
95
  renderCspContent
@@ -5,6 +5,7 @@ import { createThinHead } from "./astro/head-and-content.js";
5
5
  import { createRenderInstruction } from "./instruction.js";
6
6
  import { SERVER_ISLAND_START } from "./server-islands-shared.js";
7
7
  import { renderSlotToString } from "./slot.js";
8
+ import { toAttributeString } from "./util.js";
8
9
  const internalProps = /* @__PURE__ */ new Set([
9
10
  "server:component-path",
10
11
  "server:component-export",
@@ -148,16 +149,17 @@ class ServerIslandComponent {
148
149
  serverIslandUrl += "?" + potentialSearchParams.toString();
149
150
  this.result._metadata.extraHead.push(
150
151
  markHTMLString(
151
- `<link rel="preload" as="fetch" href="${serverIslandUrl}" crossorigin="anonymous">`
152
+ `<link rel="preload" as="fetch" href="${toAttributeString(serverIslandUrl)}" crossorigin="anonymous">`
152
153
  )
153
154
  );
154
155
  }
155
156
  const adapterHeaders = this.result.internalFetchHeaders || {};
156
157
  const headersJson = stringifyForScript(adapterHeaders);
158
+ const serverIslandUrlJson = stringifyForScript(serverIslandUrl);
157
159
  const method = useGETRequest ? (
158
160
  // GET request
159
161
  `const headers = new Headers(${headersJson});
160
- let response = await fetch('${serverIslandUrl}', { headers });`
162
+ let response = await fetch(${serverIslandUrlJson}, { headers });`
161
163
  ) : (
162
164
  // POST request
163
165
  `let data = {
@@ -166,13 +168,13 @@ let response = await fetch('${serverIslandUrl}', { headers });`
166
168
  encryptedSlots: ${stringifyForScript(slotsEncrypted)},
167
169
  };
168
170
  const headers = new Headers({ 'Content-Type': 'application/json', ...${headersJson} });
169
- let response = await fetch('${serverIslandUrl}', {
171
+ let response = await fetch(${serverIslandUrlJson}, {
170
172
  method: 'POST',
171
173
  body: JSON.stringify(data),
172
174
  headers,
173
175
  });`
174
176
  );
175
- this.islandContent = `${method}replaceServerIsland('${hostId}', response);`;
177
+ this.islandContent = `${method}replaceServerIsland(${stringifyForScript(hostId)}, response);`;
176
178
  return this.islandContent;
177
179
  }
178
180
  }
@@ -1,6 +1,6 @@
1
1
  import cssesc from "../../transitions/cssesc.js";
2
2
  import { fade, slide } from "../../transitions/index.js";
3
- import { markHTMLString } from "./escape.js";
3
+ import { escapeStyleText, markHTMLString } from "./escape.js";
4
4
  const transitionNameMap = /* @__PURE__ */ new WeakMap();
5
5
  function incrementTransitionNumber(result) {
6
6
  let num = 1;
@@ -56,7 +56,7 @@ function renderTransition(result, hash, animationName, transitionName) {
56
56
  sheet.addAnimationRaw("new", "animation: none; mix-blend-mode: normal;");
57
57
  sheet.addModern("group", "animation: none");
58
58
  }
59
- const css = sheet.toString();
59
+ const css = escapeStyleText(sheet.toString());
60
60
  result._metadata.extraHead.push(markHTMLString(`<style>${css}</style>`));
61
61
  return scope;
62
62
  }
@@ -1,4 +1,5 @@
1
1
  import type { Server } from 'node:http';
2
+ import type { CspHash } from '../core/csp/config.js';
2
3
  import type { AstroTimer } from '../core/config/timer.js';
3
4
  import type { TSConfig } from '../core/config/tsconfig.js';
4
5
  import type { AstroLogger, AstroLoggerLevel } from '../core/logger/core.js';
@@ -18,7 +19,6 @@ export type SerializedRouteData = Omit<RouteData, 'generate' | 'pattern' | 'redi
18
19
  trailingSlash: AstroConfig['trailingSlash'];
19
20
  };
20
21
  };
21
- type CspObject = Required<Exclude<AstroConfig['security']['csp'], boolean>>;
22
22
  export interface AstroSettings {
23
23
  config: AstroConfig;
24
24
  adapter: AstroAdapter | undefined;
@@ -64,7 +64,7 @@ export interface AstroSettings {
64
64
  buildOutput: undefined | 'static' | 'server';
65
65
  injectedCsp: {
66
66
  fontResources: Set<string>;
67
- styleHashes: Required<CspObject['styleDirective']>['hashes'];
67
+ styleHashes: CspHash[];
68
68
  };
69
69
  logLevel: AstroLoggerLevel;
70
70
  fontsHttpServer: Server | null;
@@ -89,4 +89,3 @@ export interface ImportedDevStyle {
89
89
  url: string;
90
90
  content: string;
91
91
  }
92
- export {};