astro 5.15.4 → 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 (33) hide show
  1. package/dist/assets/fonts/implementations/font-metrics-resolver.js +15 -9
  2. package/dist/cli/add/index.js +0 -2
  3. package/dist/cli/db/index.js +0 -2
  4. package/dist/cli/definitions.d.ts +16 -0
  5. package/dist/cli/docs/core/open-docs.d.ts +5 -3
  6. package/dist/cli/docs/core/open-docs.js +2 -9
  7. package/dist/cli/docs/definitions.d.ts +3 -11
  8. package/dist/cli/docs/domain/cloud-ide.d.ts +1 -0
  9. package/dist/cli/docs/infra/process-cloud-ide-provider.d.ts +2 -0
  10. package/dist/cli/docs/infra/process-cloud-ide-provider.js +10 -0
  11. package/dist/cli/index.js +11 -5
  12. package/dist/cli/info/index.js +0 -2
  13. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  14. package/dist/cli/infra/passthrough-text-styler.d.ts +2 -0
  15. package/dist/cli/infra/passthrough-text-styler.js +13 -0
  16. package/dist/cli/infra/process-operating-system-provider.d.ts +2 -0
  17. package/dist/cli/infra/process-operating-system-provider.js +20 -0
  18. package/dist/cli/{docs/infra → infra}/tinyexec-command-executor.js +9 -3
  19. package/dist/cli/preferences/index.js +0 -2
  20. package/dist/content/content-layer.js +3 -3
  21. package/dist/core/app/index.d.ts +15 -0
  22. package/dist/core/app/index.js +74 -14
  23. package/dist/core/app/node.js +12 -12
  24. package/dist/core/constants.js +1 -1
  25. package/dist/core/dev/dev.js +1 -1
  26. package/dist/core/messages.js +2 -2
  27. package/dist/env/vite-plugin-env.js +41 -45
  28. package/package.json +3 -3
  29. package/dist/cli/docs/domains/platform.d.ts +0 -1
  30. package/dist/cli/docs/infra/process-platform-provider.d.ts +0 -2
  31. package/dist/cli/docs/infra/process-platform-provider.js +0 -11
  32. /package/dist/cli/docs/{domains/platform.js → domain/cloud-ide.js} +0 -0
  33. /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
  };
@@ -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.4";
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",
@@ -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.4") {
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.4") {
176
- await this.#store.metaStore().set("astro-version", "5.15.4");
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.4";
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.4";
25
+ const currentVersion = "5.15.5";
26
26
  const isPrerelease = currentVersion.includes("-");
27
27
  if (!isPrerelease) {
28
28
  try {
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "5.15.4";
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.4"}`
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)};`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.15.4",
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"
@@ -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
- };