astro 5.15.3 → 5.15.5

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 (40) hide show
  1. package/dist/assets/fonts/implementations/font-metrics-resolver.js +15 -9
  2. package/dist/assets/fonts/logic/normalize-remote-font-faces.js +1 -1
  3. package/dist/assets/fonts/logic/optimize-fallbacks.js +1 -1
  4. package/dist/cli/add/index.js +0 -2
  5. package/dist/cli/db/index.js +0 -2
  6. package/dist/cli/definitions.d.ts +16 -0
  7. package/dist/cli/docs/core/open-docs.d.ts +5 -3
  8. package/dist/cli/docs/core/open-docs.js +2 -9
  9. package/dist/cli/docs/definitions.d.ts +3 -11
  10. package/dist/cli/docs/domain/cloud-ide.d.ts +1 -0
  11. package/dist/cli/docs/infra/process-cloud-ide-provider.d.ts +2 -0
  12. package/dist/cli/docs/infra/process-cloud-ide-provider.js +10 -0
  13. package/dist/cli/index.js +11 -5
  14. package/dist/cli/info/index.js +0 -2
  15. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  16. package/dist/cli/infra/passthrough-text-styler.d.ts +2 -0
  17. package/dist/cli/infra/passthrough-text-styler.js +13 -0
  18. package/dist/cli/infra/process-operating-system-provider.d.ts +2 -0
  19. package/dist/cli/infra/process-operating-system-provider.js +20 -0
  20. package/dist/cli/{docs/infra → infra}/tinyexec-command-executor.js +9 -3
  21. package/dist/cli/preferences/index.js +0 -2
  22. package/dist/container/index.d.ts +4 -1
  23. package/dist/content/content-layer.js +3 -3
  24. package/dist/core/app/index.d.ts +15 -0
  25. package/dist/core/app/index.js +74 -14
  26. package/dist/core/app/node.js +12 -12
  27. package/dist/core/constants.js +1 -1
  28. package/dist/core/dev/dev.js +1 -1
  29. package/dist/core/errors/errors-data.d.ts +3 -3
  30. package/dist/core/errors/errors-data.js +1 -1
  31. package/dist/core/messages.js +2 -2
  32. package/dist/env/vite-plugin-env.js +41 -45
  33. package/dist/virtual-modules/i18n.d.ts +87 -5
  34. package/dist/virtual-modules/i18n.js +3 -1
  35. package/package.json +3 -4
  36. package/dist/cli/docs/domains/platform.d.ts +0 -1
  37. package/dist/cli/docs/infra/process-platform-provider.d.ts +0 -2
  38. package/dist/cli/docs/infra/process-platform-provider.js +0 -11
  39. /package/dist/cli/docs/{domains/platform.js → domain/cloud-ide.js} +0 -0
  40. /package/dist/cli/{docs/infra → infra}/tinyexec-command-executor.d.ts +0 -0
@@ -15,9 +15,11 @@ function filterRequiredMetrics({
15
15
  xWidthAvg
16
16
  };
17
17
  }
18
- function toPercentage(value, fractionDigits = 4) {
19
- const percentage = value * 100;
20
- return `${+percentage.toFixed(fractionDigits)}%`;
18
+ function round(value) {
19
+ return parseFloat(value.toFixed(4));
20
+ }
21
+ function toPercentString(value) {
22
+ return `${round(value * 100)}%`;
21
23
  }
22
24
  function createCapsizeFontMetricsResolver({
23
25
  fontFetcher,
@@ -29,7 +31,8 @@ function createCapsizeFontMetricsResolver({
29
31
  cache[name] ??= filterRequiredMetrics(await fromBuffer(await fontFetcher.fetch(input)));
30
32
  return cache[name];
31
33
  },
32
- // Source: https://github.com/unjs/fontaine/blob/f00f84032c5d5da72c8798eae4cd68d3ddfbf340/src/css.ts#L170
34
+ // Adapted from Capsize
35
+ // Source: https://github.com/seek-oss/capsize/blob/b752693428b45994442433f7e3476ca4e3e3c507/packages/core/src/createFontStack.ts
33
36
  generateFontFace({
34
37
  metrics,
35
38
  fallbackMetrics,
@@ -39,18 +42,21 @@ function createCapsizeFontMetricsResolver({
39
42
  }) {
40
43
  const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
41
44
  const fallbackFontXAvgRatio = fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm;
42
- const sizeAdjust = preferredFontXAvgRatio / fallbackFontXAvgRatio;
45
+ const sizeAdjust = preferredFontXAvgRatio && fallbackFontXAvgRatio ? preferredFontXAvgRatio / fallbackFontXAvgRatio : 1;
43
46
  const adjustedEmSquare = metrics.unitsPerEm * sizeAdjust;
44
47
  const ascentOverride = metrics.ascent / adjustedEmSquare;
45
48
  const descentOverride = Math.abs(metrics.descent) / adjustedEmSquare;
46
49
  const lineGapOverride = metrics.lineGap / adjustedEmSquare;
50
+ const fallbackAscentOverride = fallbackMetrics.ascent / adjustedEmSquare;
51
+ const fallbackDescentOverride = Math.abs(fallbackMetrics.descent) / adjustedEmSquare;
52
+ const fallbackLineGapOverride = fallbackMetrics.lineGap / adjustedEmSquare;
47
53
  return cssRenderer.generateFontFace(fallbackName, {
48
54
  ...properties,
49
55
  src: renderFontSrc([{ name: fallbackFontName }]),
50
- "size-adjust": toPercentage(sizeAdjust),
51
- "ascent-override": toPercentage(ascentOverride),
52
- "descent-override": toPercentage(descentOverride),
53
- "line-gap-override": toPercentage(lineGapOverride)
56
+ "size-adjust": sizeAdjust && sizeAdjust !== 1 ? toPercentString(sizeAdjust) : void 0,
57
+ "ascent-override": ascentOverride && ascentOverride !== fallbackAscentOverride ? toPercentString(ascentOverride) : void 0,
58
+ "descent-override": descentOverride && descentOverride !== fallbackDescentOverride ? toPercentString(descentOverride) : void 0,
59
+ "line-gap-override": lineGapOverride !== fallbackLineGapOverride ? toPercentString(lineGapOverride) : void 0
54
60
  });
55
61
  }
56
62
  };
@@ -4,7 +4,7 @@ function normalizeRemoteFontFaces({
4
4
  urlProxy,
5
5
  fontTypeExtractor
6
6
  }) {
7
- return fonts.filter((font) => typeof font.meta?.priority === "number" ? font.meta.priority === 0 : true).map((font) => {
7
+ return fonts.filter((font) => typeof font.meta?.priority === "number" ? font.meta.priority <= 1 : true).map((font) => {
8
8
  let index = 0;
9
9
  return {
10
10
  ...font,
@@ -24,7 +24,7 @@ async function optimizeFallbacks({
24
24
  }
25
25
  const localFontsMappings = localFonts.map((font) => ({
26
26
  font,
27
- // We must't wrap in quote because that's handled by the CSS renderer
27
+ // We mustn't wrap in quote because that's handled by the CSS renderer
28
28
  name: `${family.nameWithHash} fallback: ${font}`
29
29
  }));
30
30
  fallbacks = [...localFontsMappings.map((m) => m.name), ...fallbacks];
@@ -24,7 +24,6 @@ import {
24
24
  import * as msg from "../../core/messages.js";
25
25
  import { printHelp } from "../../core/messages.js";
26
26
  import { appendForwardSlash } from "../../core/path.js";
27
- import { apply as applyPolyfill } from "../../core/polyfill.js";
28
27
  import { ensureProcessNodeEnv, parseNpmName } from "../../core/util.js";
29
28
  import { eventCliSession, telemetry } from "../../events/index.js";
30
29
  import { exec } from "../exec.js";
@@ -90,7 +89,6 @@ const OFFICIAL_ADAPTER_TO_IMPORT_MAP = {
90
89
  };
91
90
  async function add(names, { flags }) {
92
91
  ensureProcessNodeEnv("production");
93
- applyPolyfill();
94
92
  const inlineConfig = flagsToAstroInlineConfig(flags);
95
93
  const { userConfig } = await resolveConfig(inlineConfig, "add");
96
94
  telemetry.record(eventCliSession("add", userConfig));
@@ -1,9 +1,7 @@
1
1
  import { resolveConfig } from "../../core/config/config.js";
2
- import { apply as applyPolyfill } from "../../core/polyfill.js";
3
2
  import { createLoggerFromFlags, flagsToAstroInlineConfig } from "../flags.js";
4
3
  import { getPackage } from "../install-package.js";
5
4
  async function db({ flags }) {
6
- applyPolyfill();
7
5
  const logger = createLoggerFromFlags(flags);
8
6
  const getPackageOpts = {
9
7
  skipAsk: !!flags.yes || !!flags.y,
@@ -1,3 +1,4 @@
1
+ import type { StdioOptions } from 'node:child_process';
1
2
  import type { AnyCommand } from './domain/command.js';
2
3
  import type { HelpPayload } from './domain/help-payload.js';
3
4
  export interface HelpDisplay {
@@ -18,3 +19,18 @@ export interface AstroVersionProvider {
18
19
  export interface CommandRunner {
19
20
  run: <T extends AnyCommand>(command: T, ...args: Parameters<T['run']>) => ReturnType<T['run']>;
20
21
  }
22
+ export interface CommandExecutor {
23
+ execute: (command: string, args?: Array<string>, options?: {
24
+ cwd?: string;
25
+ env?: Record<string, string | undefined>;
26
+ shell?: boolean;
27
+ input?: string;
28
+ stdio?: StdioOptions;
29
+ }) => Promise<{
30
+ stdout: string;
31
+ }>;
32
+ }
33
+ export interface OperatingSystemProvider {
34
+ getName: () => NodeJS.Platform;
35
+ getDisplayName: () => string;
36
+ }
@@ -1,10 +1,12 @@
1
1
  import type { Logger } from '../../../core/logger/core.js';
2
- import type { CommandExecutor, PlatformProvider } from '../definitions.js';
2
+ import type { CommandExecutor, OperatingSystemProvider } from '../../definitions.js';
3
+ import type { CloudIdeProvider } from '../definitions.js';
3
4
  interface Options {
4
5
  url: string;
5
- platformProvider: PlatformProvider;
6
+ operatingSystemProvider: OperatingSystemProvider;
6
7
  logger: Logger;
7
8
  commandExecutor: CommandExecutor;
9
+ cloudIdeProvider: CloudIdeProvider;
8
10
  }
9
11
  export declare const openDocsCommand: {
10
12
  help: {
@@ -14,6 +16,6 @@ export declare const openDocsCommand: {
14
16
  };
15
17
  description: string;
16
18
  };
17
- run({ url, platformProvider, logger, commandExecutor }: Options): Promise<void>;
19
+ run({ url, operatingSystemProvider, logger, commandExecutor, cloudIdeProvider }: Options): Promise<void>;
18
20
  };
19
21
  export {};
@@ -10,13 +10,6 @@ function getExecInputForPlatform(platform) {
10
10
  return ["cmd", ["/c", "start"]];
11
11
  case "gitpod":
12
12
  return ["/ide/bin/remote-cli/gitpod-code", ["--openExternal"]];
13
- case "aix":
14
- case "freebsd":
15
- case "haiku":
16
- case "openbsd":
17
- case "sunos":
18
- case "cygwin":
19
- case "netbsd":
20
13
  default:
21
14
  return null;
22
15
  }
@@ -29,8 +22,8 @@ const openDocsCommand = defineCommand({
29
22
  },
30
23
  description: `Launches the Astro Docs website directly from the terminal.`
31
24
  },
32
- async run({ url, platformProvider, logger, commandExecutor }) {
33
- const platform = platformProvider.get();
25
+ async run({ url, operatingSystemProvider, logger, commandExecutor, cloudIdeProvider }) {
26
+ const platform = cloudIdeProvider.getName() ?? operatingSystemProvider.getName();
34
27
  const input = getExecInputForPlatform(platform);
35
28
  if (!input) {
36
29
  logger.error(
@@ -1,12 +1,4 @@
1
- import type { Platform } from './domains/platform.js';
2
- export interface PlatformProvider {
3
- get: () => Platform;
4
- }
5
- export interface CommandExecutor {
6
- execute: (command: string, args?: Array<string>, options?: {
7
- cwd?: string;
8
- env: Record<string, string | undefined>;
9
- }) => Promise<{
10
- stdout: string;
11
- }>;
1
+ import type { CloudIde } from './domain/cloud-ide.js';
2
+ export interface CloudIdeProvider {
3
+ getName: () => CloudIde | null;
12
4
  }
@@ -0,0 +1 @@
1
+ export type CloudIde = 'gitpod';
@@ -0,0 +1,2 @@
1
+ import type { CloudIdeProvider } from '../definitions.js';
2
+ export declare function createProcessCloudIdeProvider(): CloudIdeProvider;
@@ -0,0 +1,10 @@
1
+ function createProcessCloudIdeProvider() {
2
+ return {
3
+ getName() {
4
+ return Boolean(process.env.GITPOD_REPO_ROOT) ? "gitpod" : null;
5
+ }
6
+ };
7
+ }
8
+ export {
9
+ createProcessCloudIdeProvider
10
+ };
package/dist/cli/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import yargs from "yargs-parser";
2
+ import { apply as applyPolyfill } from "../core/polyfill.js";
2
3
  function resolveCommand(flags) {
3
4
  const cmd = flags._[2];
4
5
  if (flags.version) return "version";
@@ -26,6 +27,7 @@ function resolveCommand(flags) {
26
27
  return "help";
27
28
  }
28
29
  async function runCommand(cmd, flags) {
30
+ applyPolyfill();
29
31
  const [
30
32
  { createLoggerFromFlags },
31
33
  { createPicocolorsTextStyler },
@@ -81,20 +83,24 @@ async function runCommand(cmd, flags) {
81
83
  case "docs": {
82
84
  const [
83
85
  { createTinyexecCommandExecutor },
84
- { createProcessPlatformProvider },
86
+ { createProcessOperatingSystemProvider },
87
+ { createProcessCloudIdeProvider },
85
88
  { openDocsCommand }
86
89
  ] = await Promise.all([
87
- import("./docs/infra/tinyexec-command-executor.js"),
88
- import("./docs/infra/process-platform-provider.js"),
90
+ import("./infra/tinyexec-command-executor.js"),
91
+ import("./infra/process-operating-system-provider.js"),
92
+ import("./docs/infra/process-cloud-ide-provider.js"),
89
93
  import("./docs/core/open-docs.js")
90
94
  ]);
91
95
  const commandExecutor = createTinyexecCommandExecutor();
92
- const platformProvider = createProcessPlatformProvider();
96
+ const operatingSystemProvider = createProcessOperatingSystemProvider();
97
+ const cloudIdeProvider = createProcessCloudIdeProvider();
93
98
  return await runner.run(openDocsCommand, {
94
99
  url: "https://docs.astro.build/",
95
100
  logger,
96
101
  commandExecutor,
97
- platformProvider
102
+ operatingSystemProvider,
103
+ cloudIdeProvider
98
104
  });
99
105
  }
100
106
  case "telemetry": {
@@ -4,7 +4,6 @@ import colors from "picocolors";
4
4
  import prompts from "prompts";
5
5
  import { resolveConfig } from "../../core/config/index.js";
6
6
  import { ASTRO_VERSION } from "../../core/constants.js";
7
- import { apply as applyPolyfill } from "../../core/polyfill.js";
8
7
  import { flagsToAstroInlineConfig } from "../flags.js";
9
8
  async function getInfoOutput({
10
9
  userConfig,
@@ -52,7 +51,6 @@ async function getInfoOutput({
52
51
  return output.trim();
53
52
  }
54
53
  async function printInfo({ flags }) {
55
- applyPolyfill();
56
54
  const { userConfig } = await resolveConfig(flagsToAstroInlineConfig(flags), "info");
57
55
  const output = await getInfoOutput({ userConfig, print: true });
58
56
  await copyToClipboard(output, flags.copy);
@@ -1,5 +1,5 @@
1
1
  function createBuildTimeAstroVersionProvider() {
2
- const version = "5.15.3";
2
+ const version = "5.15.5";
3
3
  return {
4
4
  getVersion() {
5
5
  return version;
@@ -0,0 +1,2 @@
1
+ import type { TextStyler } from '../definitions.js';
2
+ export declare function createPassthroughTextStyler(): TextStyler;
@@ -0,0 +1,13 @@
1
+ function createPassthroughTextStyler() {
2
+ return {
3
+ bgWhite: (msg) => msg,
4
+ black: (msg) => msg,
5
+ dim: (msg) => msg,
6
+ green: (msg) => msg,
7
+ bold: (msg) => msg,
8
+ bgGreen: (msg) => msg
9
+ };
10
+ }
11
+ export {
12
+ createPassthroughTextStyler
13
+ };
@@ -0,0 +1,2 @@
1
+ import type { OperatingSystemProvider } from '../definitions.js';
2
+ export declare function createProcessOperatingSystemProvider(): OperatingSystemProvider;
@@ -0,0 +1,20 @@
1
+ const PLATFORM_TO_OS = {
2
+ darwin: "macOS",
3
+ win32: "Windows",
4
+ linux: "Linux"
5
+ };
6
+ function createProcessOperatingSystemProvider() {
7
+ const platform = process.platform;
8
+ return {
9
+ getName() {
10
+ return platform;
11
+ },
12
+ getDisplayName() {
13
+ const system = PLATFORM_TO_OS[platform] ?? platform;
14
+ return `${system} (${process.arch})`;
15
+ }
16
+ };
17
+ }
18
+ export {
19
+ createProcessOperatingSystemProvider
20
+ };
@@ -2,13 +2,19 @@ import { NonZeroExitError, x } from "tinyexec";
2
2
  function createTinyexecCommandExecutor() {
3
3
  return {
4
4
  async execute(command, args, options) {
5
- return await x(command, args, {
5
+ const proc = x(command, args, {
6
6
  throwOnError: true,
7
7
  nodeOptions: {
8
8
  cwd: options?.cwd,
9
- env: options?.env
9
+ env: options?.env,
10
+ shell: options?.shell,
11
+ stdio: options?.stdio
10
12
  }
11
- }).then(
13
+ });
14
+ if (options?.input) {
15
+ proc.process?.stdin?.end(options.input);
16
+ }
17
+ return await proc.then(
12
18
  (o) => o,
13
19
  (e) => {
14
20
  if (e instanceof NonZeroExitError) {
@@ -7,7 +7,6 @@ import { resolveConfig } from "../../core/config/config.js";
7
7
  import { createSettings } from "../../core/config/settings.js";
8
8
  import { collectErrorMetadata } from "../../core/errors/dev/utils.js";
9
9
  import * as msg from "../../core/messages.js";
10
- import { apply as applyPolyfill } from "../../core/polyfill.js";
11
10
  import { DEFAULT_PREFERENCES } from "../../preferences/defaults.js";
12
11
  import { coerce, isValidKey } from "../../preferences/index.js";
13
12
  import { createLoggerFromFlags, flagsToAstroInlineConfig } from "../flags.js";
@@ -25,7 +24,6 @@ function isValidSubcommand(subcommand) {
25
24
  return PREFERENCES_SUBCOMMANDS.includes(subcommand);
26
25
  }
27
26
  async function preferences(subcommand, key, value, { flags }) {
28
- applyPolyfill();
29
27
  if (!isValidSubcommand(subcommand) || flags?.help || flags?.h) {
30
28
  msg.printHelp({
31
29
  commandName: "astro preferences",
@@ -3,7 +3,10 @@ import type { AstroComponentFactory } from '../runtime/server/index.js';
3
3
  import type { Props } from '../types/public/common.js';
4
4
  import type { AstroUserConfig } from '../types/public/config.js';
5
5
  import type { NamedSSRLoadedRendererValue, RouteType, SSRLoadedRenderer, SSRLoadedRendererValue, SSRManifest, SSRResult } from '../types/public/internal.js';
6
- /** Public type, used for integrations to define a renderer for the container API */
6
+ /**
7
+ * Public type, used for integrations to define a renderer for the container API
8
+ * @deprecated Use `AstroRenderer` instead.
9
+ */
7
10
  export type ContainerRenderer = {
8
11
  /**
9
12
  * The name of the renderer.
@@ -164,7 +164,7 @@ ${contentConfig.error.message}`);
164
164
  logger.info("Content config changed");
165
165
  shouldClear = true;
166
166
  }
167
- if (previousAstroVersion && previousAstroVersion !== "5.15.3") {
167
+ if (previousAstroVersion && previousAstroVersion !== "5.15.5") {
168
168
  logger.info("Astro version changed");
169
169
  shouldClear = true;
170
170
  }
@@ -172,8 +172,8 @@ ${contentConfig.error.message}`);
172
172
  logger.info("Clearing content store");
173
173
  this.#store.clearAll();
174
174
  }
175
- if ("5.15.3") {
176
- await this.#store.metaStore().set("astro-version", "5.15.3");
175
+ if ("5.15.5") {
176
+ await this.#store.metaStore().set("astro-version", "5.15.5");
177
177
  }
178
178
  if (currentConfigDigest) {
179
179
  await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -71,6 +71,21 @@ export declare class App {
71
71
  protected set manifest(value: SSRManifest);
72
72
  protected matchesAllowedDomains(forwardedHost: string, protocol?: string): boolean;
73
73
  static validateForwardedHost(forwardedHost: string, allowedDomains?: Partial<RemotePattern>[], protocol?: string): boolean;
74
+ /**
75
+ * Validate a hostname by rejecting any with path separators.
76
+ * Prevents path injection attacks. Invalid hostnames return undefined.
77
+ */
78
+ static sanitizeHost(hostname: string | undefined): string | undefined;
79
+ /**
80
+ * Validate forwarded headers (proto, host, port) against allowedDomains.
81
+ * Returns validated values or undefined for rejected headers.
82
+ * Uses strict defaults: http/https only for proto, rejects port if not in allowedDomains.
83
+ */
84
+ static validateForwardedHeaders(forwardedProtocol?: string, forwardedHost?: string, forwardedPort?: string, allowedDomains?: Partial<RemotePattern>[]): {
85
+ protocol?: string;
86
+ host?: string;
87
+ port?: string;
88
+ };
74
89
  set setManifestData(newManifestData: RoutesList);
75
90
  removeBase(pathname: string): string;
76
91
  /**
@@ -83,6 +83,72 @@ class App {
83
83
  return false;
84
84
  }
85
85
  }
86
+ /**
87
+ * Validate a hostname by rejecting any with path separators.
88
+ * Prevents path injection attacks. Invalid hostnames return undefined.
89
+ */
90
+ static sanitizeHost(hostname) {
91
+ if (!hostname) return void 0;
92
+ if (/[/\\]/.test(hostname)) return void 0;
93
+ return hostname;
94
+ }
95
+ /**
96
+ * Validate forwarded headers (proto, host, port) against allowedDomains.
97
+ * Returns validated values or undefined for rejected headers.
98
+ * Uses strict defaults: http/https only for proto, rejects port if not in allowedDomains.
99
+ */
100
+ static validateForwardedHeaders(forwardedProtocol, forwardedHost, forwardedPort, allowedDomains) {
101
+ const result = {};
102
+ if (forwardedProtocol) {
103
+ if (allowedDomains && allowedDomains.length > 0) {
104
+ const hasProtocolPatterns = allowedDomains.some(
105
+ (pattern) => pattern.protocol !== void 0
106
+ );
107
+ if (hasProtocolPatterns) {
108
+ try {
109
+ const testUrl = new URL(`${forwardedProtocol}://example.com`);
110
+ const isAllowed = allowedDomains.some((pattern) => matchPattern(testUrl, pattern));
111
+ if (isAllowed) {
112
+ result.protocol = forwardedProtocol;
113
+ }
114
+ } catch {
115
+ }
116
+ } else if (/^https?$/.test(forwardedProtocol)) {
117
+ result.protocol = forwardedProtocol;
118
+ }
119
+ } else if (/^https?$/.test(forwardedProtocol)) {
120
+ result.protocol = forwardedProtocol;
121
+ }
122
+ }
123
+ if (forwardedPort && allowedDomains && allowedDomains.length > 0) {
124
+ const hasPortPatterns = allowedDomains.some((pattern) => pattern.port !== void 0);
125
+ if (hasPortPatterns) {
126
+ const isAllowed = allowedDomains.some((pattern) => pattern.port === forwardedPort);
127
+ if (isAllowed) {
128
+ result.port = forwardedPort;
129
+ }
130
+ }
131
+ }
132
+ if (forwardedHost && forwardedHost.length > 0 && allowedDomains && allowedDomains.length > 0) {
133
+ const protoForValidation = result.protocol || "https";
134
+ const sanitized = App.sanitizeHost(forwardedHost);
135
+ if (sanitized) {
136
+ try {
137
+ const hostnameOnly = sanitized.split(":")[0];
138
+ const portFromHost = sanitized.includes(":") ? sanitized.split(":")[1] : void 0;
139
+ const portForValidation = result.port || portFromHost;
140
+ const hostWithPort = portForValidation ? `${hostnameOnly}:${portForValidation}` : hostnameOnly;
141
+ const testUrl = new URL(`${protoForValidation}://${hostWithPort}`);
142
+ const isAllowed = allowedDomains.some((pattern) => matchPattern(testUrl, pattern));
143
+ if (isAllowed) {
144
+ result.host = sanitized;
145
+ }
146
+ } catch {
147
+ }
148
+ }
149
+ }
150
+ return result;
151
+ }
86
152
  /**
87
153
  * Creates a pipeline by reading the stored manifest
88
154
  *
@@ -165,20 +231,14 @@ class App {
165
231
  let pathname = void 0;
166
232
  const url = new URL(request.url);
167
233
  if (this.#manifest.i18n && (this.#manifest.i18n.strategy === "domains-prefix-always" || this.#manifest.i18n.strategy === "domains-prefix-other-locales" || this.#manifest.i18n.strategy === "domains-prefix-always-no-redirect")) {
168
- let forwardedHost = request.headers.get("X-Forwarded-Host");
169
- let protocol = request.headers.get("X-Forwarded-Proto");
170
- if (protocol) {
171
- protocol = protocol + ":";
172
- } else {
173
- protocol = url.protocol;
174
- }
175
- if (forwardedHost && !this.matchesAllowedDomains(forwardedHost, protocol?.replace(":", ""))) {
176
- forwardedHost = null;
177
- }
178
- let host = forwardedHost;
179
- if (!host) {
180
- host = request.headers.get("Host");
181
- }
234
+ const validated = App.validateForwardedHeaders(
235
+ request.headers.get("X-Forwarded-Proto") ?? void 0,
236
+ request.headers.get("X-Forwarded-Host") ?? void 0,
237
+ request.headers.get("X-Forwarded-Port") ?? void 0,
238
+ this.#manifest.allowedDomains
239
+ );
240
+ let protocol = validated.protocol ? validated.protocol + ":" : url.protocol;
241
+ let host = validated.host ?? request.headers.get("Host");
182
242
  if (host && protocol) {
183
243
  host = host.split(":")[0];
184
244
  try {
@@ -49,20 +49,20 @@ class NodeApp extends App {
49
49
  const getFirstForwardedValue = (multiValueHeader) => {
50
50
  return multiValueHeader?.toString()?.split(",").map((e) => e.trim())?.[0];
51
51
  };
52
- const forwardedProtocol = getFirstForwardedValue(req.headers["x-forwarded-proto"]);
53
52
  const providedProtocol = isEncrypted ? "https" : "http";
54
- const protocol = forwardedProtocol ?? providedProtocol;
55
- let forwardedHostname = getFirstForwardedValue(req.headers["x-forwarded-host"]);
56
53
  const providedHostname = req.headers.host ?? req.headers[":authority"];
57
- if (forwardedHostname && !App.validateForwardedHost(
58
- forwardedHostname,
59
- allowedDomains,
60
- forwardedProtocol ?? providedProtocol
61
- )) {
62
- forwardedHostname = void 0;
63
- }
64
- const hostname = forwardedHostname ?? providedHostname;
65
- const port = getFirstForwardedValue(req.headers["x-forwarded-port"]);
54
+ const validated = App.validateForwardedHeaders(
55
+ getFirstForwardedValue(req.headers["x-forwarded-proto"]),
56
+ getFirstForwardedValue(req.headers["x-forwarded-host"]),
57
+ getFirstForwardedValue(req.headers["x-forwarded-port"]),
58
+ allowedDomains
59
+ );
60
+ const protocol = validated.protocol ?? providedProtocol;
61
+ const sanitizedProvidedHostname = App.sanitizeHost(
62
+ typeof providedHostname === "string" ? providedHostname : void 0
63
+ );
64
+ const hostname = validated.host ?? sanitizedProvidedHostname;
65
+ const port = validated.port;
66
66
  let url;
67
67
  try {
68
68
  const hostnamePort = getHostnamePort(hostname, port);
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "5.15.3";
1
+ const ASTRO_VERSION = "5.15.5";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
4
4
  const REWRITE_DIRECTIVE_HEADER_VALUE = "yes";
@@ -22,7 +22,7 @@ async function dev(inlineConfig) {
22
22
  await telemetry.record([]);
23
23
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
24
24
  const logger = restart.container.logger;
25
- const currentVersion = "5.15.3";
25
+ const currentVersion = "5.15.5";
26
26
  const isPrerelease = currentVersion.includes("-");
27
27
  if (!isPrerelease) {
28
28
  try {
@@ -1176,7 +1176,7 @@ export declare const UnknownFilesystemError: {
1176
1176
  * @description
1177
1177
  * Cannot extract the font type from the given URL.
1178
1178
  * @message
1179
- * An error occured while trying to extract the font type from the given URL.
1179
+ * An error occurred while trying to extract the font type from the given URL.
1180
1180
  */
1181
1181
  export declare const CannotExtractFontType: {
1182
1182
  name: string;
@@ -1189,7 +1189,7 @@ export declare const CannotExtractFontType: {
1189
1189
  * @description
1190
1190
  * Cannot determine weight and style from font file, update your family config and set `weight` and `style` manually instead.
1191
1191
  * @message
1192
- * An error occured while determining the weight and style from the local font file.
1192
+ * An error occurred while determining the weight and style from the local font file.
1193
1193
  */
1194
1194
  export declare const CannotDetermineWeightAndStyleFromFontFile: {
1195
1195
  name: string;
@@ -1202,7 +1202,7 @@ export declare const CannotDetermineWeightAndStyleFromFontFile: {
1202
1202
  * @description
1203
1203
  * Cannot fetch the given font file
1204
1204
  * @message
1205
- * An error occured while fetching font file from the given URL.
1205
+ * An error occurred while fetching font file from the given URL.
1206
1206
  */
1207
1207
  export declare const CannotFetchFontFile: {
1208
1208
  name: string;
@@ -455,7 +455,7 @@ const CannotFetchFontFile = {
455
455
  const CannotLoadFontProvider = {
456
456
  name: "CannotLoadFontProvider",
457
457
  title: "Cannot load font provider",
458
- message: (entrypoint) => `An error occured while loading the "${entrypoint}" provider.`,
458
+ message: (entrypoint) => `An error occurred while loading the "${entrypoint}" provider.`,
459
459
  hint: "This is an issue with the font provider. Please open an issue on their repository."
460
460
  };
461
461
  const ExperimentalFontsNotEnabled = {
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "5.15.3";
41
+ const version = "5.15.5";
42
42
  const localPrefix = `${dim("\u2503")} Local `;
43
43
  const networkPrefix = `${dim("\u2503")} Network `;
44
44
  const emptyPrefix = " ".repeat(11);
@@ -275,7 +275,7 @@ function printHelp({
275
275
  message.push(
276
276
  linebreak(),
277
277
  ` ${bgGreen(black(` ${commandName} `))} ${green(
278
- `v${"5.15.3"}`
278
+ `v${"5.15.5"}`
279
279
  )} ${headline}`
280
280
  );
281
281
  }
@@ -13,42 +13,13 @@ import { invalidVariablesToError } from "./errors.js";
13
13
  import { getEnvFieldType, validateEnvVariable } from "./validators.js";
14
14
  function astroEnv({ settings, sync, envLoader }) {
15
15
  const { schema, validateSecrets } = settings.config.env;
16
- let isDev;
17
- let templates = null;
18
- function ensureTemplateAreLoaded() {
19
- if (templates !== null) {
20
- return;
21
- }
22
- const loadedEnv = envLoader.get();
23
- if (!isDev) {
24
- for (const [key, value] of Object.entries(loadedEnv)) {
25
- if (value !== void 0) {
26
- process.env[key] = value;
27
- }
28
- }
29
- }
30
- const validatedVariables = validatePublicVariables({
31
- schema,
32
- loadedEnv,
33
- validateSecrets,
34
- sync
35
- });
36
- templates = {
37
- ...getTemplates(schema, validatedVariables, isDev ? loadedEnv : null),
38
- internal: `export const schema = ${JSON.stringify(schema)};`
39
- };
40
- }
16
+ let isBuild;
17
+ let populated = false;
41
18
  return {
42
19
  name: "astro-env-plugin",
43
20
  enforce: "pre",
44
21
  config(_, { command }) {
45
- isDev = command !== "build";
46
- },
47
- buildStart() {
48
- ensureTemplateAreLoaded();
49
- },
50
- buildEnd() {
51
- templates = null;
22
+ isBuild = command === "build";
52
23
  },
53
24
  resolveId(id) {
54
25
  if (id === CLIENT_VIRTUAL_MODULE_ID) {
@@ -62,23 +33,43 @@ function astroEnv({ settings, sync, envLoader }) {
62
33
  }
63
34
  },
64
35
  load(id, options) {
65
- if (id === RESOLVED_CLIENT_VIRTUAL_MODULE_ID) {
66
- ensureTemplateAreLoaded();
67
- return { code: templates.client };
36
+ if (id === RESOLVED_INTERNAL_VIRTUAL_MODULE_ID) {
37
+ return { code: `export const schema = ${JSON.stringify(schema)};` };
68
38
  }
69
- if (id === RESOLVED_SERVER_VIRTUAL_MODULE_ID) {
70
- if (options?.ssr) {
71
- ensureTemplateAreLoaded();
72
- return { code: templates.server };
73
- }
39
+ if (id === RESOLVED_SERVER_VIRTUAL_MODULE_ID && !options?.ssr) {
74
40
  throw new AstroError({
75
41
  ...AstroErrorData.ServerOnlyModule,
76
42
  message: AstroErrorData.ServerOnlyModule.message(SERVER_VIRTUAL_MODULE_ID)
77
43
  });
78
44
  }
79
- if (id === RESOLVED_INTERNAL_VIRTUAL_MODULE_ID) {
80
- ensureTemplateAreLoaded();
81
- return { code: templates.internal };
45
+ if (id === RESOLVED_CLIENT_VIRTUAL_MODULE_ID || id === RESOLVED_SERVER_VIRTUAL_MODULE_ID) {
46
+ const loadedEnv = envLoader.get();
47
+ if (isBuild && !populated) {
48
+ for (const [key, value] of Object.entries(loadedEnv)) {
49
+ if (value !== void 0) {
50
+ process.env[key] = value;
51
+ }
52
+ }
53
+ populated = true;
54
+ }
55
+ const validatedVariables = validatePublicVariables({
56
+ schema,
57
+ loadedEnv,
58
+ validateSecrets,
59
+ sync
60
+ });
61
+ const { client, server } = getTemplates({
62
+ schema,
63
+ validatedVariables,
64
+ // In dev, we inline process.env to avoid freezing it
65
+ loadedEnv: isBuild ? null : loadedEnv
66
+ });
67
+ if (id === RESOLVED_CLIENT_VIRTUAL_MODULE_ID) {
68
+ return { code: client };
69
+ }
70
+ if (id === RESOLVED_SERVER_VIRTUAL_MODULE_ID) {
71
+ return { code: server };
72
+ }
82
73
  }
83
74
  }
84
75
  };
@@ -112,9 +103,14 @@ function validatePublicVariables({
112
103
  }
113
104
  return valid;
114
105
  }
115
- function getTemplates(schema, validatedVariables, loadedEnv) {
106
+ let cachedServerTemplate;
107
+ function getTemplates({
108
+ schema,
109
+ validatedVariables,
110
+ loadedEnv
111
+ }) {
116
112
  let client = "";
117
- let server = readFileSync(MODULE_TEMPLATE_URL, "utf-8");
113
+ let server = cachedServerTemplate ??= readFileSync(MODULE_TEMPLATE_URL, "utf-8");
118
114
  let onSetGetEnv = "";
119
115
  for (const { key, value, context } of validatedVariables) {
120
116
  const str = `export const ${key} = ${JSON.stringify(value)};`;
@@ -3,7 +3,6 @@ import * as I18nInternals from '../i18n/index.js';
3
3
  import type { MiddlewareHandler } from '../types/public/common.js';
4
4
  import type { AstroConfig, ValidRedirectStatus } from '../types/public/config.js';
5
5
  import type { APIContext } from '../types/public/context.js';
6
- export { normalizeTheLocale, toCodes, toPaths } from '../i18n/index.js';
7
6
  export type GetLocaleOptions = I18nInternals.GetLocaleOptions;
8
7
  /**
9
8
  * @param locale A locale
@@ -155,9 +154,9 @@ export declare const getLocaleByPath: (path: string) => string;
155
154
  *
156
155
  * ```js
157
156
  * import { pathHasLocale } from "astro:i18n";
158
- * getLocaleByPath("italiano"); // returns `true`
159
- * getLocaleByPath("es"); // returns `true`
160
- * getLocaleByPath("it-VT"); // returns `false`
157
+ * pathHasLocale("italiano"); // returns `true`
158
+ * pathHasLocale("es"); // returns `true`
159
+ * pathHasLocale("it-VT"); // returns `false`
161
160
  * ```
162
161
  */
163
162
  export declare const pathHasLocale: (path: string) => boolean;
@@ -189,7 +188,7 @@ export declare let notFound: (context: APIContext, response?: Response) => Respo
189
188
  */
190
189
  export declare let requestHasLocale: (context: APIContext) => boolean;
191
190
  /**
192
- * Allows to use the build-in fallback system of Astro
191
+ * Allows to use the built-in fallback system of Astro
193
192
  *
194
193
  * @param {APIContext} context The context passed to the middleware
195
194
  * @param {Promise<Response>} response An optional `Response` in case you're handling a `Response` coming from the `next` function.
@@ -228,3 +227,86 @@ type NewAstroRoutingConfigWithoutManual = OnlyObject<NonNullable<AstroConfig['i1
228
227
  * ```
229
228
  */
230
229
  export declare let middleware: (customOptions: NewAstroRoutingConfigWithoutManual) => MiddlewareHandler;
230
+ /**
231
+ * Replaces underscores (`_`) with hyphens (`-`) in the given locale before
232
+ * returning a lowercase version.
233
+ *
234
+ * @param {string} locale A locale code to normalize.
235
+ * @returns {string} The normalized locale.
236
+ *
237
+ * ## Example
238
+ *
239
+ * ```js
240
+ * import { normalizeTheLocale } from "astro:i18n";
241
+ * normalizeTheLocale("it_VT") // returns `it-vt`
242
+ * ```
243
+ */
244
+ export declare const normalizeTheLocale: typeof I18nInternals.normalizeTheLocale;
245
+ /**
246
+ * Retrieves the configured locale codes for each locale defined in your
247
+ * configuration. When multiple codes are associated to a locale, only the
248
+ * first one will be added to the array.
249
+ *
250
+ * @param {Locales} locales The configured locales.
251
+ * @returns {string[]} The configured locales codes.
252
+ *
253
+ * ## Example
254
+ *
255
+ * Given the following configuration:
256
+ *
257
+ * ```js
258
+ * // astro.config.mjs
259
+ *
260
+ * export default defineConfig({
261
+ * i18n: {
262
+ * locales: [
263
+ * { codes: ["it-VT", "it"], path: "italiano" },
264
+ * "es"
265
+ * ]
266
+ * }
267
+ * })
268
+ * ```
269
+ *
270
+ * Here's a use case:
271
+ *
272
+ * ```js
273
+ * import { i18n } from "astro:config/client";
274
+ * import { toCodes } from "astro:i18n";
275
+ * toCodes(i18n.locales); // ["it-VT", "es"]
276
+ * ```
277
+ */
278
+ export declare const toCodes: typeof I18nInternals.toCodes;
279
+ /**
280
+ * Retrieves the configured locale paths for each locale defined in your
281
+ * configuration.
282
+ *
283
+ * @param {Locales} locales The configured locales.
284
+ * @returns {string[]} The configured locales paths.
285
+ *
286
+ * ## Example
287
+ *
288
+ * Given the following configuration:
289
+ *
290
+ * ```js
291
+ * // astro.config.mjs
292
+ *
293
+ * export default defineConfig({
294
+ * i18n: {
295
+ * locales: [
296
+ * { codes: ["it-VT", "it"], path: "italiano" },
297
+ * "es"
298
+ * ]
299
+ * }
300
+ * })
301
+ * ```
302
+ *
303
+ * Here's a use case:
304
+ *
305
+ * ```js
306
+ * import { i18n } from "astro:config/client";
307
+ * import { toPaths } from "astro:i18n";
308
+ * toPaths(i18n.locales); // ["italiano", "es"]
309
+ * ```
310
+ */
311
+ export declare const toPaths: typeof I18nInternals.toPaths;
312
+ export {};
@@ -2,7 +2,6 @@ import { IncorrectStrategyForI18n } from "../core/errors/errors-data.js";
2
2
  import { AstroError } from "../core/errors/index.js";
3
3
  import * as I18nInternals from "../i18n/index.js";
4
4
  import { toFallbackType, toRoutingStrategy } from "../i18n/utils.js";
5
- import { normalizeTheLocale, toCodes, toPaths } from "../i18n/index.js";
6
5
  const { trailingSlash, format, site, i18n, isBuild } = (
7
6
  // @ts-expect-error
8
7
  __ASTRO_INTERNAL_I18N_CONFIG__
@@ -141,6 +140,9 @@ if (i18n?.routing === "manual") {
141
140
  } else {
142
141
  middleware = noop("middleware");
143
142
  }
143
+ const normalizeTheLocale = I18nInternals.normalizeTheLocale;
144
+ const toCodes = I18nInternals.toCodes;
145
+ const toPaths = I18nInternals.toPaths;
144
146
  export {
145
147
  getAbsoluteLocaleUrl,
146
148
  getAbsoluteLocaleUrlList,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.15.3",
3
+ "version": "5.15.5",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -154,8 +154,8 @@
154
154
  "zod-to-json-schema": "^3.24.6",
155
155
  "zod-to-ts": "^1.2.0",
156
156
  "@astrojs/internal-helpers": "0.7.4",
157
- "@astrojs/markdown-remark": "6.3.8",
158
- "@astrojs/telemetry": "3.3.0"
157
+ "@astrojs/telemetry": "3.3.0",
158
+ "@astrojs/markdown-remark": "6.3.8"
159
159
  },
160
160
  "optionalDependencies": {
161
161
  "sharp": "^0.34.0"
@@ -178,7 +178,6 @@
178
178
  "@types/yargs-parser": "^21.0.3",
179
179
  "cheerio": "1.1.2",
180
180
  "eol": "^0.10.0",
181
- "execa": "^8.0.1",
182
181
  "expect-type": "^1.2.2",
183
182
  "fs-fixture": "^2.8.1",
184
183
  "mdast-util-mdx": "^3.0.0",
@@ -1 +0,0 @@
1
- export type Platform = NodeJS.Platform | 'gitpod';
@@ -1,2 +0,0 @@
1
- import type { PlatformProvider } from '../definitions.js';
2
- export declare function createProcessPlatformProvider(): PlatformProvider;
@@ -1,11 +0,0 @@
1
- function createProcessPlatformProvider() {
2
- const platform = Boolean(process.env.GITPOD_REPO_ROOT) ? "gitpod" : process.platform;
3
- return {
4
- get() {
5
- return platform;
6
- }
7
- };
8
- }
9
- export {
10
- createProcessPlatformProvider
11
- };