astro 4.8.7 → 4.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,234 @@
1
+ import { posix } from "node:path";
2
+ import { validateConfig } from "../core/config/config.js";
3
+ import { ASTRO_CONFIG_DEFAULTS } from "../core/config/schema.js";
4
+ import { Logger } from "../core/logger/core.js";
5
+ import { nodeLogDestination } from "../core/logger/node.js";
6
+ import { removeLeadingForwardSlash } from "../core/path.js";
7
+ import { RenderContext } from "../core/render-context.js";
8
+ import { getParts, getPattern, validateSegment } from "../core/routing/manifest/create.js";
9
+ import { ContainerPipeline } from "./pipeline.js";
10
+ function createManifest(renderers, manifest, middleware) {
11
+ const defaultMiddleware = (_, next) => {
12
+ return next();
13
+ };
14
+ return {
15
+ rewritingEnabled: false,
16
+ trailingSlash: manifest?.trailingSlash ?? ASTRO_CONFIG_DEFAULTS.trailingSlash,
17
+ buildFormat: manifest?.buildFormat ?? ASTRO_CONFIG_DEFAULTS.build.format,
18
+ compressHTML: manifest?.compressHTML ?? ASTRO_CONFIG_DEFAULTS.compressHTML,
19
+ assets: manifest?.assets ?? /* @__PURE__ */ new Set(),
20
+ assetsPrefix: manifest?.assetsPrefix ?? void 0,
21
+ entryModules: manifest?.entryModules ?? {},
22
+ routes: manifest?.routes ?? [],
23
+ adapterName: "",
24
+ clientDirectives: manifest?.clientDirectives ?? /* @__PURE__ */ new Map(),
25
+ renderers: manifest?.renderers ?? renderers,
26
+ base: manifest?.base ?? ASTRO_CONFIG_DEFAULTS.base,
27
+ componentMetadata: manifest?.componentMetadata ?? /* @__PURE__ */ new Map(),
28
+ inlinedScripts: manifest?.inlinedScripts ?? /* @__PURE__ */ new Map(),
29
+ i18n: manifest?.i18n,
30
+ checkOrigin: false,
31
+ middleware: manifest?.middleware ?? middleware ?? defaultMiddleware
32
+ };
33
+ }
34
+ class experimental_AstroContainer {
35
+ #pipeline;
36
+ /**
37
+ * Internally used to check if the container was created with a manifest.
38
+ * @private
39
+ */
40
+ #withManifest = false;
41
+ constructor({
42
+ streaming = false,
43
+ renderers = [],
44
+ manifest,
45
+ resolve
46
+ }) {
47
+ this.#pipeline = ContainerPipeline.create({
48
+ logger: new Logger({
49
+ level: "info",
50
+ dest: nodeLogDestination
51
+ }),
52
+ manifest: createManifest(renderers, manifest),
53
+ streaming,
54
+ serverLike: true,
55
+ renderers,
56
+ resolve: async (specifier) => {
57
+ if (this.#withManifest) {
58
+ return this.#containerResolve(specifier);
59
+ } else if (resolve) {
60
+ return resolve(specifier);
61
+ }
62
+ return specifier;
63
+ }
64
+ });
65
+ }
66
+ async #containerResolve(specifier) {
67
+ const found = this.#pipeline.manifest.entryModules[specifier];
68
+ if (found) {
69
+ return new URL(found, ASTRO_CONFIG_DEFAULTS.build.client).toString();
70
+ }
71
+ return found;
72
+ }
73
+ /**
74
+ * Creates a new instance of a container.
75
+ *
76
+ * @param {AstroContainerOptions=} containerOptions
77
+ */
78
+ static async create(containerOptions = {}) {
79
+ const { streaming = false, renderers = [] } = containerOptions;
80
+ const loadedRenderers = await Promise.all(
81
+ renderers.map(async (renderer) => {
82
+ const mod = await import(renderer.serverEntrypoint);
83
+ if (typeof mod.default !== "undefined") {
84
+ return {
85
+ ...renderer,
86
+ ssr: mod.default
87
+ };
88
+ }
89
+ return void 0;
90
+ })
91
+ );
92
+ const finalRenderers = loadedRenderers.filter((r) => Boolean(r));
93
+ return new experimental_AstroContainer({ streaming, renderers: finalRenderers });
94
+ }
95
+ // NOTE: we keep this private via TS instead via `#` so it's still available on the surface, so we can play with it.
96
+ // @ematipico: I plan to use it for a possible integration that could help people
97
+ static async createFromManifest(manifest) {
98
+ const config = await validateConfig(ASTRO_CONFIG_DEFAULTS, process.cwd(), "container");
99
+ const container = new experimental_AstroContainer({
100
+ manifest
101
+ });
102
+ container.#withManifest = true;
103
+ return container;
104
+ }
105
+ #insertRoute({
106
+ path,
107
+ componentInstance,
108
+ params = {},
109
+ type = "page"
110
+ }) {
111
+ const pathUrl = new URL(path, "https://example.com");
112
+ const routeData = this.#createRoute(pathUrl, params, type);
113
+ this.#pipeline.manifest.routes.push({
114
+ routeData,
115
+ file: "",
116
+ links: [],
117
+ styles: [],
118
+ scripts: []
119
+ });
120
+ this.#pipeline.insertRoute(routeData, componentInstance);
121
+ return routeData;
122
+ }
123
+ /**
124
+ * @description
125
+ * It renders a component and returns the result as a string.
126
+ *
127
+ * ## Example
128
+ *
129
+ * ```js
130
+ * import Card from "../src/components/Card.astro";
131
+ *
132
+ * const container = await AstroContainer.create();
133
+ * const result = await container.renderToString(Card);
134
+ *
135
+ * console.log(result); // it's a string
136
+ * ```
137
+ *
138
+ *
139
+ * @param {AstroComponentFactory} component The instance of the component.
140
+ * @param {ContainerRenderOptions=} options Possible options to pass when rendering the component.
141
+ */
142
+ async renderToString(component, options = {}) {
143
+ const response = await this.renderToResponse(component, options);
144
+ return await response.text();
145
+ }
146
+ /**
147
+ * @description
148
+ * It renders a component and returns the `Response` as result of the rendering phase.
149
+ *
150
+ * ## Example
151
+ *
152
+ * ```js
153
+ * import Card from "../src/components/Card.astro";
154
+ *
155
+ * const container = await AstroContainer.create();
156
+ * const response = await container.renderToResponse(Card);
157
+ *
158
+ * console.log(response.status); // it's a number
159
+ * ```
160
+ *
161
+ *
162
+ * @param {AstroComponentFactory} component The instance of the component.
163
+ * @param {ContainerRenderOptions=} options Possible options to pass when rendering the component.
164
+ */
165
+ async renderToResponse(component, options = {}) {
166
+ const { routeType = "page", slots } = options;
167
+ const request = options?.request ?? new Request("https://example.com/");
168
+ const url = new URL(request.url);
169
+ const componentInstance = routeType === "endpoint" ? component : this.#wrapComponent(component, options.params);
170
+ const routeData = this.#insertRoute({
171
+ path: request.url,
172
+ componentInstance,
173
+ params: options.params,
174
+ type: routeType
175
+ });
176
+ const renderContext = RenderContext.create({
177
+ pipeline: this.#pipeline,
178
+ routeData,
179
+ status: 200,
180
+ middleware: this.#pipeline.middleware,
181
+ request,
182
+ pathname: url.pathname,
183
+ locals: options?.locals ?? {}
184
+ });
185
+ if (options.params) {
186
+ renderContext.params = options.params;
187
+ }
188
+ return renderContext.render(componentInstance, slots);
189
+ }
190
+ #createRoute(url, params, type) {
191
+ const segments = removeLeadingForwardSlash(url.pathname).split(posix.sep).filter(Boolean).map((s) => {
192
+ validateSegment(s);
193
+ return getParts(s, url.pathname);
194
+ });
195
+ return {
196
+ route: url.pathname,
197
+ component: "",
198
+ generate(_data) {
199
+ return "";
200
+ },
201
+ params: Object.keys(params),
202
+ pattern: getPattern(
203
+ segments,
204
+ ASTRO_CONFIG_DEFAULTS.base,
205
+ ASTRO_CONFIG_DEFAULTS.trailingSlash
206
+ ),
207
+ prerender: false,
208
+ segments,
209
+ type,
210
+ fallbackRoutes: [],
211
+ isIndex: false
212
+ };
213
+ }
214
+ /**
215
+ * If the provided component isn't a default export, the function wraps it in an object `{default: Component }` to mimic the default export.
216
+ * @param componentFactory
217
+ * @param params
218
+ * @private
219
+ */
220
+ #wrapComponent(componentFactory, params) {
221
+ if (params) {
222
+ return {
223
+ default: componentFactory,
224
+ getStaticPaths() {
225
+ return [{ params }];
226
+ }
227
+ };
228
+ }
229
+ return { default: componentFactory };
230
+ }
231
+ }
232
+ export {
233
+ experimental_AstroContainer
234
+ };
@@ -0,0 +1,11 @@
1
+ import type { ComponentInstance, RewritePayload, RouteData, SSRResult } from '../@types/astro.js';
2
+ import { type HeadElements, Pipeline } from '../core/base-pipeline.js';
3
+ export declare class ContainerPipeline extends Pipeline {
4
+ #private;
5
+ static create({ logger, manifest, renderers, resolve, serverLike, streaming, }: Pick<ContainerPipeline, 'logger' | 'manifest' | 'renderers' | 'resolve' | 'serverLike' | 'streaming'>): ContainerPipeline;
6
+ componentMetadata(_routeData: RouteData): Promise<SSRResult['componentMetadata']> | void;
7
+ headElements(routeData: RouteData): Promise<HeadElements> | HeadElements;
8
+ tryRewrite(rewritePayload: RewritePayload): Promise<[RouteData, ComponentInstance]>;
9
+ insertRoute(route: RouteData, componentInstance: ComponentInstance): void;
10
+ getComponentByRoute(_routeData: RouteData): Promise<ComponentInstance>;
11
+ }
@@ -0,0 +1,96 @@
1
+ import { Pipeline } from "../core/base-pipeline.js";
2
+ import { RouteNotFound } from "../core/errors/errors-data.js";
3
+ import { AstroError } from "../core/errors/index.js";
4
+ import {
5
+ createModuleScriptElement,
6
+ createStylesheetElementSet
7
+ } from "../core/render/ssr-element.js";
8
+ class ContainerPipeline extends Pipeline {
9
+ /**
10
+ * Internal cache to store components instances by `RouteData`.
11
+ * @private
12
+ */
13
+ #componentsInterner = /* @__PURE__ */ new WeakMap();
14
+ static create({
15
+ logger,
16
+ manifest,
17
+ renderers,
18
+ resolve,
19
+ serverLike,
20
+ streaming
21
+ }) {
22
+ return new ContainerPipeline(
23
+ logger,
24
+ manifest,
25
+ "development",
26
+ renderers,
27
+ resolve,
28
+ serverLike,
29
+ streaming
30
+ );
31
+ }
32
+ componentMetadata(_routeData) {
33
+ }
34
+ headElements(routeData) {
35
+ const routeInfo = this.manifest.routes.find((route) => route.routeData === routeData);
36
+ const links = /* @__PURE__ */ new Set();
37
+ const scripts = /* @__PURE__ */ new Set();
38
+ const styles = createStylesheetElementSet(routeInfo?.styles ?? []);
39
+ for (const script of routeInfo?.scripts ?? []) {
40
+ if ("stage" in script) {
41
+ if (script.stage === "head-inline") {
42
+ scripts.add({
43
+ props: {},
44
+ children: script.children
45
+ });
46
+ }
47
+ } else {
48
+ scripts.add(createModuleScriptElement(script));
49
+ }
50
+ }
51
+ return { links, styles, scripts };
52
+ }
53
+ async tryRewrite(rewritePayload) {
54
+ let foundRoute;
55
+ for (const route of this.manifest.routes) {
56
+ const routeData = route.routeData;
57
+ if (rewritePayload instanceof URL) {
58
+ if (routeData.pattern.test(rewritePayload.pathname)) {
59
+ foundRoute = routeData;
60
+ break;
61
+ }
62
+ } else if (rewritePayload instanceof Request) {
63
+ const url = new URL(rewritePayload.url);
64
+ if (routeData.pattern.test(url.pathname)) {
65
+ foundRoute = routeData;
66
+ break;
67
+ }
68
+ } else if (routeData.pattern.test(decodeURI(rewritePayload))) {
69
+ foundRoute = routeData;
70
+ break;
71
+ }
72
+ }
73
+ if (foundRoute) {
74
+ const componentInstance = await this.getComponentByRoute(foundRoute);
75
+ return [foundRoute, componentInstance];
76
+ } else {
77
+ throw new AstroError(RouteNotFound);
78
+ }
79
+ }
80
+ insertRoute(route, componentInstance) {
81
+ this.#componentsInterner.set(route, {
82
+ page() {
83
+ return Promise.resolve(componentInstance);
84
+ },
85
+ renderers: this.manifest.renderers,
86
+ onRequest: this.manifest.middleware
87
+ });
88
+ }
89
+ // At the moment it's not used by the container via any public API
90
+ // @ts-expect-error It needs to be implemented.
91
+ async getComponentByRoute(_routeData) {
92
+ }
93
+ }
94
+ export {
95
+ ContainerPipeline
96
+ };
@@ -48,7 +48,10 @@ class NodeApp extends App {
48
48
  Object.assign(options, makeRequestBody(req));
49
49
  }
50
50
  const request = new Request(url, options);
51
- if (req.socket?.remoteAddress) {
51
+ const clientIp = req.headers["x-forwarded-for"];
52
+ if (clientIp) {
53
+ Reflect.set(request, clientAddressSymbol, clientIp);
54
+ } else if (req.socket?.remoteAddress) {
52
55
  Reflect.set(request, clientAddressSymbol, req.socket.remoteAddress);
53
56
  }
54
57
  return request;
@@ -91,7 +91,7 @@ ${bgGreen(black(` ${verb} static routes `))}`);
91
91
  if (ssr) {
92
92
  for (const [pageData, filePath] of pagesToGenerate) {
93
93
  if (pageData.route.prerender) {
94
- if (config.experimental.i18nDomains) {
94
+ if (config.i18n?.domains && Object.keys(config.i18n.domains).length > 0) {
95
95
  throw new AstroError({
96
96
  ...NoPrerenderedRoutesWithDomains,
97
97
  message: NoPrerenderedRoutesWithDomains.message(pageData.component)
@@ -210,7 +210,7 @@ async function getPathsForRoute(route, mod, pipeline, builtPaths) {
210
210
  const label = staticPaths.length === 1 ? "page" : "pages";
211
211
  logger.debug(
212
212
  "build",
213
- `\u251C\u2500\u2500 ${bold(green("\u2714"))} ${route.component} \u2192 ${magenta(`[${staticPaths.length} ${label}]`)}`
213
+ `\u251C\u2500\u2500 ${bold(green("\u221A"))} ${route.component} \u2192 ${magenta(`[${staticPaths.length} ${label}]`)}`
214
214
  );
215
215
  paths = staticPaths.map((staticPath) => {
216
216
  try {
@@ -388,7 +388,7 @@ function createBuildManifest(settings, internals, renderers, middleware) {
388
388
  buildFormat: settings.config.build.format,
389
389
  middleware,
390
390
  rewritingEnabled: settings.config.experimental.rewriting,
391
- checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false
391
+ checkOrigin: settings.config.security?.checkOrigin ?? false
392
392
  };
393
393
  }
394
394
  export {
@@ -178,7 +178,7 @@ function buildManifest(opts, internals, staticFiles) {
178
178
  });
179
179
  }
180
180
  const i18n = settings.config.i18n;
181
- if (settings.config.experimental.i18nDomains && i18n && i18n.domains) {
181
+ if (i18n && i18n.domains) {
182
182
  for (const [locale, domainValue] of Object.entries(i18n.domains)) {
183
183
  domainLookupTable[domainValue] = normalizeTheLocale(locale);
184
184
  }
@@ -212,7 +212,7 @@ function buildManifest(opts, internals, staticFiles) {
212
212
  assets: staticFiles.map(prefixAssetPath),
213
213
  i18n: i18nManifest,
214
214
  buildFormat: settings.config.build.format,
215
- checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
215
+ checkOrigin: settings.config.security?.checkOrigin ?? false,
216
216
  rewritingEnabled: settings.config.experimental.rewriting
217
217
  };
218
218
  }