astro 6.2.2 → 6.3.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.
- package/dist/actions/handler.d.ts +32 -0
- package/dist/actions/handler.js +45 -0
- package/dist/actions/runtime/server.js +1 -1
- package/dist/assets/build/generate.js +1 -1
- package/dist/assets/build/remote.d.ts +3 -2
- package/dist/assets/build/remote.js +16 -9
- package/dist/assets/endpoint/generic.js +4 -7
- package/dist/assets/endpoint/shared.js +7 -2
- package/dist/assets/index.d.ts +1 -0
- package/dist/assets/index.js +2 -0
- package/dist/assets/services/sharp.js +7 -0
- package/dist/assets/utils/index.d.ts +1 -0
- package/dist/assets/utils/index.js +2 -0
- package/dist/assets/utils/redirectValidation.d.ts +48 -0
- package/dist/assets/utils/redirectValidation.js +48 -0
- package/dist/assets/utils/remoteProbe.js +25 -2
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/container/index.js +18 -14
- package/dist/content/content-layer.js +3 -4
- package/dist/content/server-listeners.js +0 -4
- package/dist/content/vite-plugin-content-virtual-mod.js +9 -1
- package/dist/core/app/base.d.ts +33 -15
- package/dist/core/app/base.js +120 -324
- package/dist/core/app/dev/app.d.ts +3 -2
- package/dist/core/app/dev/app.js +4 -60
- package/dist/core/app/entrypoints/virtual/dev.js +2 -0
- package/dist/core/app/entrypoints/virtual/prod.js +4 -1
- package/dist/core/app/prepare-response.d.ts +11 -0
- package/dist/core/app/prepare-response.js +18 -0
- package/dist/core/app/render-options.d.ts +11 -0
- package/dist/core/app/render-options.js +11 -0
- package/dist/core/base-pipeline.d.ts +38 -1
- package/dist/core/base-pipeline.js +50 -7
- package/dist/core/build/app.d.ts +3 -4
- package/dist/core/build/app.js +3 -17
- package/dist/core/cache/handler.d.ts +29 -0
- package/dist/core/cache/handler.js +81 -0
- package/dist/core/config/schemas/base.d.ts +4 -0
- package/dist/core/config/schemas/base.js +4 -0
- package/dist/core/config/schemas/relative.d.ts +6 -0
- package/dist/core/constants.d.ts +27 -1
- package/dist/core/constants.js +14 -1
- package/dist/core/cookies/cookies.d.ts +7 -2
- package/dist/core/cookies/cookies.js +11 -4
- package/dist/core/cookies/response.d.ts +1 -1
- package/dist/core/cookies/response.js +1 -2
- package/dist/core/create-vite.js +15 -0
- package/dist/core/csp/runtime.js +6 -4
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/errors/build-handler.d.ts +17 -0
- package/dist/core/errors/build-handler.js +22 -0
- package/dist/core/errors/default-handler.d.ts +14 -0
- package/dist/core/errors/default-handler.js +144 -0
- package/dist/core/errors/dev-handler.d.ts +21 -0
- package/dist/core/errors/dev-handler.js +82 -0
- package/dist/core/errors/handler.d.ts +9 -0
- package/dist/core/errors/handler.js +0 -0
- package/dist/core/fetch/default-handler.d.ts +17 -0
- package/dist/core/fetch/default-handler.js +45 -0
- package/dist/core/fetch/fetch-state.d.ts +244 -0
- package/dist/core/fetch/fetch-state.js +779 -0
- package/dist/core/fetch/index.d.ts +61 -0
- package/dist/core/fetch/index.js +121 -0
- package/dist/core/fetch/types.d.ts +6 -0
- package/dist/core/fetch/types.js +0 -0
- package/dist/core/fetch/vite-plugin.d.ts +5 -0
- package/dist/core/fetch/vite-plugin.js +69 -0
- package/dist/core/hono/index.d.ts +21 -0
- package/dist/core/hono/index.js +98 -0
- package/dist/core/i18n/handler.d.ts +18 -0
- package/dist/core/i18n/handler.js +119 -0
- package/dist/core/logger/core.d.ts +8 -0
- package/dist/core/logger/core.js +16 -0
- package/dist/core/messages/runtime.js +1 -1
- package/dist/core/middleware/astro-middleware.d.ts +27 -0
- package/dist/core/middleware/astro-middleware.js +53 -0
- package/dist/core/pages/handler.d.ts +20 -0
- package/dist/core/pages/handler.js +74 -0
- package/dist/core/redirects/render.d.ts +2 -2
- package/dist/core/redirects/render.js +7 -8
- package/dist/core/rewrites/handler.d.ts +37 -0
- package/dist/core/rewrites/handler.js +67 -0
- package/dist/core/routing/3xx.js +8 -4
- package/dist/core/routing/handler.d.ts +17 -0
- package/dist/core/routing/handler.js +172 -0
- package/dist/core/routing/match.d.ts +0 -7
- package/dist/core/routing/match.js +0 -5
- package/dist/core/routing/trailing-slash-handler.d.ts +18 -0
- package/dist/core/routing/trailing-slash-handler.js +67 -0
- package/dist/core/session/drivers.d.ts +1 -1
- package/dist/core/session/handler.d.ts +11 -0
- package/dist/core/session/handler.js +33 -0
- package/dist/core/util/normalized-url.d.ts +10 -0
- package/dist/core/util/normalized-url.js +21 -0
- package/dist/i18n/middleware.d.ts +10 -0
- package/dist/i18n/middleware.js +4 -88
- package/dist/i18n/utils.js +2 -2
- package/dist/runtime/server/astro-island.js +57 -20
- package/dist/runtime/server/astro-island.prebuilt-dev.d.ts +1 -1
- package/dist/runtime/server/astro-island.prebuilt-dev.js +1 -1
- package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
- package/dist/runtime/server/astro-island.prebuilt.js +1 -1
- package/dist/runtime/server/render/server-islands.js +2 -1
- package/dist/types/public/config.d.ts +46 -12
- package/dist/types/public/internal.d.ts +1 -1
- package/dist/vite-plugin-app/app.d.ts +4 -5
- package/dist/vite-plugin-app/app.js +20 -65
- package/package.json +11 -5
- package/dist/core/render-context.d.ts +0 -77
- package/dist/core/render-context.js +0 -826
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import colors from "piccolore";
|
|
2
|
+
import {
|
|
3
|
+
collapseDuplicateLeadingSlashes,
|
|
4
|
+
prependForwardSlash,
|
|
5
|
+
removeTrailingForwardSlash
|
|
6
|
+
} from "@astrojs/internal-helpers/path";
|
|
7
|
+
import { deserializeActionResult } from "../../actions/runtime/client.js";
|
|
8
|
+
import { createCallAction, createGetActionResult, hasActionPayload } from "../../actions/utils.js";
|
|
9
|
+
import { AstroCookies } from "../cookies/index.js";
|
|
10
|
+
import { Slots } from "../render/index.js";
|
|
11
|
+
import {
|
|
12
|
+
ASTRO_GENERATOR,
|
|
13
|
+
DEFAULT_404_COMPONENT,
|
|
14
|
+
fetchStateSymbol,
|
|
15
|
+
originPathnameSymbol,
|
|
16
|
+
pipelineSymbol,
|
|
17
|
+
responseSentSymbol
|
|
18
|
+
} from "../constants.js";
|
|
19
|
+
import { pushDirective } from "../csp/runtime.js";
|
|
20
|
+
import { generateCspDigest } from "../encryption.js";
|
|
21
|
+
import { AstroError, AstroErrorData } from "../errors/index.js";
|
|
22
|
+
import {
|
|
23
|
+
computeCurrentLocale as computeCurrentLocaleUtil,
|
|
24
|
+
computeCurrentLocaleFromParams,
|
|
25
|
+
computePreferredLocale as computePreferredLocaleUtil,
|
|
26
|
+
computePreferredLocaleList as computePreferredLocaleListUtil
|
|
27
|
+
} from "../../i18n/utils.js";
|
|
28
|
+
import { getParams, getProps } from "../render/index.js";
|
|
29
|
+
import { Rewrites } from "../rewrites/handler.js";
|
|
30
|
+
import { isRoute404or500, isRouteServerIsland } from "../routing/match.js";
|
|
31
|
+
import { normalizeUrl } from "../util/normalized-url.js";
|
|
32
|
+
import { getOriginPathname, setOriginPathname } from "../routing/rewrite.js";
|
|
33
|
+
import { routeHasHtmlExtension } from "../routing/helpers.js";
|
|
34
|
+
import { getRenderOptions } from "../app/render-options.js";
|
|
35
|
+
function getFetchStateFromAPIContext(context) {
|
|
36
|
+
const state = context[fetchStateSymbol];
|
|
37
|
+
if (!state) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"FetchState not found on APIContext. This is an internal error \u2014 the context was not created through Astro's request pipeline."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return state;
|
|
43
|
+
}
|
|
44
|
+
class FetchState {
|
|
45
|
+
pipeline;
|
|
46
|
+
/**
|
|
47
|
+
* The request to render. Mutated during rewrites so subsequent renders
|
|
48
|
+
* see the rewritten URL.
|
|
49
|
+
*/
|
|
50
|
+
request;
|
|
51
|
+
routeData;
|
|
52
|
+
/**
|
|
53
|
+
* The pathname to use for routing and rendering. Starts out as the raw,
|
|
54
|
+
* base-stripped, decoded pathname from the request. May be further
|
|
55
|
+
* normalized by `AstroHandler` after routeData is known (in dev, when
|
|
56
|
+
* the matched route has no `.html` extension, `.html` / `/index.html`
|
|
57
|
+
* suffixes are stripped).
|
|
58
|
+
*/
|
|
59
|
+
pathname;
|
|
60
|
+
/** Resolved render options (addCookieHeader, clientAddress, locals, etc.). */
|
|
61
|
+
renderOptions;
|
|
62
|
+
/** When the request started, used to log duration. */
|
|
63
|
+
timeStart;
|
|
64
|
+
/**
|
|
65
|
+
* The route's loaded component module. Set before middleware runs; may
|
|
66
|
+
* be swapped during in-flight rewrites from inside the middleware chain.
|
|
67
|
+
*/
|
|
68
|
+
componentInstance;
|
|
69
|
+
/**
|
|
70
|
+
* Slot overrides supplied by the container API. `undefined` for HTTP
|
|
71
|
+
* requests — `PagesHandler` coalesces to `{}` on read so we don't
|
|
72
|
+
* allocate an empty object per request.
|
|
73
|
+
*/
|
|
74
|
+
slots;
|
|
75
|
+
/**
|
|
76
|
+
* Default HTTP status for the rendered response. Callers override
|
|
77
|
+
* before rendering runs (e.g. `AstroHandler` sets this from
|
|
78
|
+
* `BaseApp.getDefaultStatusCode`; error handlers set `404` / `500`).
|
|
79
|
+
*/
|
|
80
|
+
status = 200;
|
|
81
|
+
/** Whether user middleware should be skipped for this request. */
|
|
82
|
+
skipMiddleware = false;
|
|
83
|
+
/** A flag that tells the render content if the rewriting was triggered. */
|
|
84
|
+
isRewriting = false;
|
|
85
|
+
/** A safety net in case of loops (rewrite counter). */
|
|
86
|
+
counter = 0;
|
|
87
|
+
/** Cookies for this request. Created lazily on first access. */
|
|
88
|
+
cookies;
|
|
89
|
+
/** Route params derived from routeData + pathname. Computed lazily. */
|
|
90
|
+
#params;
|
|
91
|
+
get params() {
|
|
92
|
+
if (!this.#params && this.routeData) {
|
|
93
|
+
this.#params = getParams(this.routeData, this.pathname);
|
|
94
|
+
}
|
|
95
|
+
return this.#params;
|
|
96
|
+
}
|
|
97
|
+
set params(value) {
|
|
98
|
+
this.#params = value;
|
|
99
|
+
}
|
|
100
|
+
/** Normalized URL for this request. */
|
|
101
|
+
url;
|
|
102
|
+
/** Client address for this request. */
|
|
103
|
+
clientAddress;
|
|
104
|
+
/** Whether this is a partial render (container API). */
|
|
105
|
+
partial;
|
|
106
|
+
/** Whether to inject CSP meta tags. */
|
|
107
|
+
shouldInjectCspMetaTags;
|
|
108
|
+
/** Request-scoped locals object, shared with user middleware. */
|
|
109
|
+
locals = {};
|
|
110
|
+
/**
|
|
111
|
+
* Memoized `props` (see `getProps`). `null` means "not yet computed"
|
|
112
|
+
* — using `null` (rather than `undefined`) keeps the hidden class
|
|
113
|
+
* stable and distinct from a valid-but-empty result.
|
|
114
|
+
*/
|
|
115
|
+
props = null;
|
|
116
|
+
/** Memoized `ActionAPIContext` (see `getActionAPIContext`). */
|
|
117
|
+
actionApiContext = null;
|
|
118
|
+
/** Memoized `APIContext` (see `getAPIContext`). */
|
|
119
|
+
apiContext = null;
|
|
120
|
+
/** Registered context providers keyed by name. Lazy-initialized on first provide(). */
|
|
121
|
+
#providers;
|
|
122
|
+
/** Cached values from resolved providers. Lazy-initialized on first resolve(). */
|
|
123
|
+
#providersResolvedValues;
|
|
124
|
+
/** Cached promise for lazy component instance loading. */
|
|
125
|
+
#componentInstancePromise;
|
|
126
|
+
/** SSR result for the current page render. */
|
|
127
|
+
result;
|
|
128
|
+
/** Initial props (from container/error handler). */
|
|
129
|
+
initialProps = {};
|
|
130
|
+
/** Rewrites handler instance. Lazy-initialized on first rewrite(). */
|
|
131
|
+
#rewrites;
|
|
132
|
+
/** Memoized Astro page partial. */
|
|
133
|
+
#astroPagePartial;
|
|
134
|
+
/** Memoized current locale. */
|
|
135
|
+
#currentLocale;
|
|
136
|
+
/** Memoized preferred locale. */
|
|
137
|
+
#preferredLocale;
|
|
138
|
+
/** Memoized preferred locale list. */
|
|
139
|
+
#preferredLocaleList;
|
|
140
|
+
constructor(pipeline, request, options) {
|
|
141
|
+
this.pipeline = pipeline;
|
|
142
|
+
this.request = request;
|
|
143
|
+
options ??= getRenderOptions(request);
|
|
144
|
+
this.routeData = options?.routeData;
|
|
145
|
+
this.renderOptions = options ?? {
|
|
146
|
+
addCookieHeader: false,
|
|
147
|
+
clientAddress: void 0,
|
|
148
|
+
locals: void 0,
|
|
149
|
+
prerenderedErrorPageFetch: fetch,
|
|
150
|
+
routeData: void 0,
|
|
151
|
+
waitUntil: void 0
|
|
152
|
+
};
|
|
153
|
+
this.componentInstance = void 0;
|
|
154
|
+
this.slots = void 0;
|
|
155
|
+
const url = new URL(request.url);
|
|
156
|
+
this.pathname = this.#computePathname(url);
|
|
157
|
+
this.timeStart = performance.now();
|
|
158
|
+
this.clientAddress = options?.clientAddress;
|
|
159
|
+
this.locals = options?.locals ?? {};
|
|
160
|
+
this.url = normalizeUrl(url);
|
|
161
|
+
this.cookies = new AstroCookies(request);
|
|
162
|
+
if (!Reflect.get(request, originPathnameSymbol)) {
|
|
163
|
+
setOriginPathname(
|
|
164
|
+
request,
|
|
165
|
+
this.pathname,
|
|
166
|
+
pipeline.manifest.trailingSlash,
|
|
167
|
+
pipeline.manifest.buildFormat
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
this.#resolveRouteData();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Triggers a rewrite. Delegates to the Rewrites handler.
|
|
174
|
+
*/
|
|
175
|
+
rewrite(payload) {
|
|
176
|
+
return (this.#rewrites ??= new Rewrites()).execute(this, payload);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Creates the SSR result for the current page render.
|
|
180
|
+
*/
|
|
181
|
+
async createResult(mod, ctx) {
|
|
182
|
+
const pipeline = this.pipeline;
|
|
183
|
+
const { clientDirectives, inlinedScripts, compressHTML, manifest, renderers, resolve } = pipeline;
|
|
184
|
+
const routeData = this.routeData;
|
|
185
|
+
const { links, scripts, styles } = await pipeline.headElements(routeData);
|
|
186
|
+
const extraStyleHashes = [];
|
|
187
|
+
const extraScriptHashes = [];
|
|
188
|
+
const shouldInjectCspMetaTags = this.shouldInjectCspMetaTags ?? manifest.shouldInjectCspMetaTags;
|
|
189
|
+
const cspAlgorithm = manifest.csp?.algorithm ?? "SHA-256";
|
|
190
|
+
if (shouldInjectCspMetaTags) {
|
|
191
|
+
for (const style of styles) {
|
|
192
|
+
extraStyleHashes.push(await generateCspDigest(style.children, cspAlgorithm));
|
|
193
|
+
}
|
|
194
|
+
for (const script of scripts) {
|
|
195
|
+
extraScriptHashes.push(await generateCspDigest(script.children, cspAlgorithm));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const componentMetadata = await pipeline.componentMetadata(routeData) ?? manifest.componentMetadata;
|
|
199
|
+
const headers = new Headers({ "Content-Type": "text/html" });
|
|
200
|
+
const partial = typeof this.partial === "boolean" ? this.partial : Boolean(mod.partial);
|
|
201
|
+
const actionResult = hasActionPayload(this.locals) ? deserializeActionResult(this.locals._actionPayload.actionResult) : void 0;
|
|
202
|
+
const status = this.status;
|
|
203
|
+
const response = {
|
|
204
|
+
status: actionResult?.error ? actionResult?.error.status : status,
|
|
205
|
+
statusText: actionResult?.error ? actionResult?.error.type : "OK",
|
|
206
|
+
get headers() {
|
|
207
|
+
return headers;
|
|
208
|
+
},
|
|
209
|
+
set headers(_) {
|
|
210
|
+
throw new AstroError(AstroErrorData.AstroResponseHeadersReassigned);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const state = this;
|
|
214
|
+
const result = {
|
|
215
|
+
base: manifest.base,
|
|
216
|
+
userAssetsBase: manifest.userAssetsBase,
|
|
217
|
+
cancelled: false,
|
|
218
|
+
clientDirectives,
|
|
219
|
+
inlinedScripts,
|
|
220
|
+
componentMetadata,
|
|
221
|
+
compressHTML,
|
|
222
|
+
cookies: this.cookies,
|
|
223
|
+
createAstro: (props, slots) => state.createAstro(result, props, slots, ctx),
|
|
224
|
+
links,
|
|
225
|
+
// SAFETY: createResult is only called after route resolution, so routeData
|
|
226
|
+
// is always set and the params getter always returns a value.
|
|
227
|
+
params: this.params,
|
|
228
|
+
partial,
|
|
229
|
+
pathname: this.pathname,
|
|
230
|
+
renderers,
|
|
231
|
+
resolve,
|
|
232
|
+
response,
|
|
233
|
+
request: this.request,
|
|
234
|
+
scripts,
|
|
235
|
+
styles,
|
|
236
|
+
actionResult,
|
|
237
|
+
async getServerIslandNameMap() {
|
|
238
|
+
const serverIslands = await pipeline.getServerIslands();
|
|
239
|
+
return serverIslands.serverIslandNameMap ?? /* @__PURE__ */ new Map();
|
|
240
|
+
},
|
|
241
|
+
key: manifest.key,
|
|
242
|
+
trailingSlash: manifest.trailingSlash,
|
|
243
|
+
_experimentalQueuedRendering: {
|
|
244
|
+
pool: pipeline.nodePool,
|
|
245
|
+
htmlStringCache: pipeline.htmlStringCache,
|
|
246
|
+
enabled: manifest.experimentalQueuedRendering?.enabled,
|
|
247
|
+
poolSize: manifest.experimentalQueuedRendering?.poolSize,
|
|
248
|
+
contentCache: manifest.experimentalQueuedRendering?.contentCache
|
|
249
|
+
},
|
|
250
|
+
_metadata: {
|
|
251
|
+
hasHydrationScript: false,
|
|
252
|
+
rendererSpecificHydrationScripts: /* @__PURE__ */ new Set(),
|
|
253
|
+
hasRenderedHead: false,
|
|
254
|
+
renderedScripts: /* @__PURE__ */ new Set(),
|
|
255
|
+
hasDirectives: /* @__PURE__ */ new Set(),
|
|
256
|
+
hasRenderedServerIslandRuntime: false,
|
|
257
|
+
headInTree: false,
|
|
258
|
+
extraHead: [],
|
|
259
|
+
extraStyleHashes,
|
|
260
|
+
extraScriptHashes,
|
|
261
|
+
propagators: /* @__PURE__ */ new Set(),
|
|
262
|
+
templateDepth: 0
|
|
263
|
+
},
|
|
264
|
+
cspDestination: manifest.csp?.cspDestination ?? (routeData.prerender ? "meta" : "header"),
|
|
265
|
+
shouldInjectCspMetaTags,
|
|
266
|
+
cspAlgorithm,
|
|
267
|
+
scriptHashes: manifest.csp?.scriptHashes ? [...manifest.csp.scriptHashes] : [],
|
|
268
|
+
scriptResources: manifest.csp?.scriptResources ? [...manifest.csp.scriptResources] : [],
|
|
269
|
+
styleHashes: manifest.csp?.styleHashes ? [...manifest.csp.styleHashes] : [],
|
|
270
|
+
styleResources: manifest.csp?.styleResources ? [...manifest.csp.styleResources] : [],
|
|
271
|
+
directives: manifest.csp?.directives ? [...manifest.csp.directives] : [],
|
|
272
|
+
isStrictDynamic: manifest.csp?.isStrictDynamic ?? false,
|
|
273
|
+
internalFetchHeaders: manifest.internalFetchHeaders
|
|
274
|
+
};
|
|
275
|
+
this.result = result;
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Creates the Astro global object for a component render.
|
|
280
|
+
*/
|
|
281
|
+
createAstro(result, props, slotValues, apiContext) {
|
|
282
|
+
let astroPagePartial;
|
|
283
|
+
if (this.isRewriting) {
|
|
284
|
+
this.#astroPagePartial = this.createAstroPagePartial(result, apiContext);
|
|
285
|
+
}
|
|
286
|
+
this.#astroPagePartial ??= this.createAstroPagePartial(result, apiContext);
|
|
287
|
+
astroPagePartial = this.#astroPagePartial;
|
|
288
|
+
const astroComponentPartial = { props, self: null };
|
|
289
|
+
const Astro = Object.assign(
|
|
290
|
+
Object.create(astroPagePartial),
|
|
291
|
+
astroComponentPartial
|
|
292
|
+
);
|
|
293
|
+
let _slots;
|
|
294
|
+
Object.defineProperty(Astro, "slots", {
|
|
295
|
+
get: () => {
|
|
296
|
+
if (!_slots) {
|
|
297
|
+
_slots = new Slots(
|
|
298
|
+
result,
|
|
299
|
+
slotValues,
|
|
300
|
+
this.pipeline.logger
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return _slots;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
return Astro;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Creates the Astro page-level partial (prototype for Astro global).
|
|
310
|
+
*/
|
|
311
|
+
createAstroPagePartial(result, apiContext) {
|
|
312
|
+
const state = this;
|
|
313
|
+
const { cookies, locals, params, pipeline, url } = this;
|
|
314
|
+
const { response } = result;
|
|
315
|
+
const redirect = (path, status = 302) => {
|
|
316
|
+
if (state.request[responseSentSymbol]) {
|
|
317
|
+
throw new AstroError({
|
|
318
|
+
...AstroErrorData.ResponseSentError
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return new Response(null, { status, headers: { Location: path } });
|
|
322
|
+
};
|
|
323
|
+
const rewrite = async (reroutePayload) => {
|
|
324
|
+
return await state.rewrite(reroutePayload);
|
|
325
|
+
};
|
|
326
|
+
const callAction = createCallAction(apiContext);
|
|
327
|
+
const partial = {
|
|
328
|
+
generator: ASTRO_GENERATOR,
|
|
329
|
+
routePattern: this.routeData.route,
|
|
330
|
+
isPrerendered: this.routeData.prerender,
|
|
331
|
+
cookies,
|
|
332
|
+
get clientAddress() {
|
|
333
|
+
return state.getClientAddress();
|
|
334
|
+
},
|
|
335
|
+
get currentLocale() {
|
|
336
|
+
return state.computeCurrentLocale();
|
|
337
|
+
},
|
|
338
|
+
params,
|
|
339
|
+
get preferredLocale() {
|
|
340
|
+
return state.computePreferredLocale();
|
|
341
|
+
},
|
|
342
|
+
get preferredLocaleList() {
|
|
343
|
+
return state.computePreferredLocaleList();
|
|
344
|
+
},
|
|
345
|
+
locals,
|
|
346
|
+
redirect,
|
|
347
|
+
rewrite,
|
|
348
|
+
request: this.request,
|
|
349
|
+
response,
|
|
350
|
+
site: pipeline.site,
|
|
351
|
+
getActionResult: createGetActionResult(locals),
|
|
352
|
+
get callAction() {
|
|
353
|
+
return callAction;
|
|
354
|
+
},
|
|
355
|
+
url,
|
|
356
|
+
get originPathname() {
|
|
357
|
+
return getOriginPathname(state.request);
|
|
358
|
+
},
|
|
359
|
+
get csp() {
|
|
360
|
+
return state.getCsp();
|
|
361
|
+
},
|
|
362
|
+
get logger() {
|
|
363
|
+
return {
|
|
364
|
+
info(msg) {
|
|
365
|
+
pipeline.logger.info(null, msg);
|
|
366
|
+
},
|
|
367
|
+
warn(msg) {
|
|
368
|
+
pipeline.logger.warn(null, msg);
|
|
369
|
+
},
|
|
370
|
+
error(msg) {
|
|
371
|
+
pipeline.logger.error(null, msg);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
this.defineProviderGetters(partial);
|
|
377
|
+
return partial;
|
|
378
|
+
}
|
|
379
|
+
getClientAddress() {
|
|
380
|
+
const { pipeline, clientAddress } = this;
|
|
381
|
+
const routeData = this.routeData;
|
|
382
|
+
if (routeData.prerender) {
|
|
383
|
+
throw new AstroError({
|
|
384
|
+
...AstroErrorData.PrerenderClientAddressNotAvailable,
|
|
385
|
+
message: AstroErrorData.PrerenderClientAddressNotAvailable.message(routeData.component)
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
if (clientAddress) {
|
|
389
|
+
return clientAddress;
|
|
390
|
+
}
|
|
391
|
+
if (pipeline.adapterName) {
|
|
392
|
+
throw new AstroError({
|
|
393
|
+
...AstroErrorData.ClientAddressNotAvailable,
|
|
394
|
+
message: AstroErrorData.ClientAddressNotAvailable.message(pipeline.adapterName)
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable);
|
|
398
|
+
}
|
|
399
|
+
getCookies() {
|
|
400
|
+
return this.cookies;
|
|
401
|
+
}
|
|
402
|
+
getCsp() {
|
|
403
|
+
const state = this;
|
|
404
|
+
const { pipeline } = this;
|
|
405
|
+
if (!pipeline.manifest.csp) {
|
|
406
|
+
if (pipeline.runtimeMode === "production") {
|
|
407
|
+
pipeline.logger.warn(
|
|
408
|
+
"csp",
|
|
409
|
+
`context.csp was used when rendering the route ${colors.green(state.routeData.route)}, but CSP was not configured. For more information, see https://docs.astro.build/en/reference/configuration-reference/#securitycsp`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
return void 0;
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
insertDirective(payload) {
|
|
416
|
+
if (state?.result?.directives) {
|
|
417
|
+
state.result.directives = pushDirective(state.result.directives, payload);
|
|
418
|
+
} else {
|
|
419
|
+
state?.result?.directives.push(payload);
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
insertScriptResource(resource) {
|
|
423
|
+
state.result?.scriptResources.push(resource);
|
|
424
|
+
},
|
|
425
|
+
insertStyleResource(resource) {
|
|
426
|
+
state.result?.styleResources.push(resource);
|
|
427
|
+
},
|
|
428
|
+
insertStyleHash(hash) {
|
|
429
|
+
state.result?.styleHashes.push(hash);
|
|
430
|
+
},
|
|
431
|
+
insertScriptHash(hash) {
|
|
432
|
+
state.result?.scriptHashes.push(hash);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
computeCurrentLocale() {
|
|
437
|
+
const {
|
|
438
|
+
url,
|
|
439
|
+
pipeline: { i18n },
|
|
440
|
+
routeData
|
|
441
|
+
} = this;
|
|
442
|
+
if (!i18n || !routeData) return;
|
|
443
|
+
const { defaultLocale, locales, strategy } = i18n;
|
|
444
|
+
const fallbackTo = strategy === "pathname-prefix-other-locales" || strategy === "domains-prefix-other-locales" ? defaultLocale : void 0;
|
|
445
|
+
if (this.#currentLocale) {
|
|
446
|
+
return this.#currentLocale;
|
|
447
|
+
}
|
|
448
|
+
let computedLocale;
|
|
449
|
+
if (isRouteServerIsland(routeData)) {
|
|
450
|
+
let referer = this.request.headers.get("referer");
|
|
451
|
+
if (referer) {
|
|
452
|
+
if (URL.canParse(referer)) {
|
|
453
|
+
referer = new URL(referer).pathname;
|
|
454
|
+
}
|
|
455
|
+
computedLocale = computeCurrentLocaleUtil(referer, locales, defaultLocale);
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
let pathname = routeData.pathname;
|
|
459
|
+
if (url && !routeData.pattern.test(url.pathname)) {
|
|
460
|
+
for (const fallbackRoute of routeData.fallbackRoutes) {
|
|
461
|
+
if (fallbackRoute.pattern.test(url.pathname)) {
|
|
462
|
+
pathname = fallbackRoute.pathname;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
pathname = pathname && !isRoute404or500(routeData) ? pathname : url.pathname ?? this.pathname;
|
|
468
|
+
computedLocale = computeCurrentLocaleUtil(pathname, locales, defaultLocale);
|
|
469
|
+
if (routeData.params.length > 0) {
|
|
470
|
+
const localeFromParams = computeCurrentLocaleFromParams(this.params, locales);
|
|
471
|
+
if (localeFromParams) {
|
|
472
|
+
computedLocale = localeFromParams;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
this.#currentLocale = computedLocale ?? fallbackTo;
|
|
477
|
+
return this.#currentLocale;
|
|
478
|
+
}
|
|
479
|
+
computePreferredLocale() {
|
|
480
|
+
const {
|
|
481
|
+
pipeline: { i18n },
|
|
482
|
+
request
|
|
483
|
+
} = this;
|
|
484
|
+
if (!i18n) return;
|
|
485
|
+
return this.#preferredLocale ??= computePreferredLocaleUtil(request, i18n.locales);
|
|
486
|
+
}
|
|
487
|
+
computePreferredLocaleList() {
|
|
488
|
+
const {
|
|
489
|
+
pipeline: { i18n },
|
|
490
|
+
request
|
|
491
|
+
} = this;
|
|
492
|
+
if (!i18n) return;
|
|
493
|
+
return this.#preferredLocaleList ??= computePreferredLocaleListUtil(request, i18n.locales);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Lazily loads the route's component module. Returns the cached
|
|
497
|
+
* instance if already loaded. The promise is cached so concurrent
|
|
498
|
+
* callers share the same load.
|
|
499
|
+
*/
|
|
500
|
+
async loadComponentInstance() {
|
|
501
|
+
if (this.componentInstance) return this.componentInstance;
|
|
502
|
+
if (this.#componentInstancePromise) return this.#componentInstancePromise;
|
|
503
|
+
this.#componentInstancePromise = this.pipeline.getComponentByRoute(this.routeData).then((mod) => {
|
|
504
|
+
this.componentInstance = mod;
|
|
505
|
+
return mod;
|
|
506
|
+
});
|
|
507
|
+
return this.#componentInstancePromise;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Registers a context provider under the given key. Handlers call
|
|
511
|
+
* this to contribute values to the request context (e.g. sessions).
|
|
512
|
+
* The `create` factory is called lazily on the first `resolve(key)`.
|
|
513
|
+
*/
|
|
514
|
+
provide(key, provider) {
|
|
515
|
+
(this.#providers ??= /* @__PURE__ */ new Map()).set(key, provider);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Lazily resolves a provider registered under `key`. Calls
|
|
519
|
+
* `provider.create()` on first access and caches the result.
|
|
520
|
+
* Returns `undefined` if no provider was registered for the key.
|
|
521
|
+
*/
|
|
522
|
+
resolve(key) {
|
|
523
|
+
if (this.#providersResolvedValues?.has(key)) {
|
|
524
|
+
return this.#providersResolvedValues.get(key);
|
|
525
|
+
}
|
|
526
|
+
const provider = this.#providers?.get(key);
|
|
527
|
+
if (!provider) return void 0;
|
|
528
|
+
const value = provider.create();
|
|
529
|
+
(this.#providersResolvedValues ??= /* @__PURE__ */ new Map()).set(key, value);
|
|
530
|
+
return value;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Runs all registered `finalize` callbacks. Should be called after
|
|
534
|
+
* the response is produced, typically in a `finally` block.
|
|
535
|
+
*
|
|
536
|
+
* Returns synchronously (no promise allocation) when nothing needs
|
|
537
|
+
* finalizing — important for the hot path where sessions are not used.
|
|
538
|
+
*/
|
|
539
|
+
finalizeAll() {
|
|
540
|
+
if (!this.#providersResolvedValues || this.#providersResolvedValues.size === 0) return;
|
|
541
|
+
let chain;
|
|
542
|
+
for (const [key, provider] of this.#providers) {
|
|
543
|
+
if (provider.finalize && this.#providersResolvedValues.has(key)) {
|
|
544
|
+
const result = provider.finalize(this.#providersResolvedValues.get(key));
|
|
545
|
+
if (result) {
|
|
546
|
+
chain = chain ? chain.then(() => result) : result;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return chain;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Adds lazy getters to `target` for each registered provider key.
|
|
554
|
+
* Used by context creation (APIContext, Astro global) so that
|
|
555
|
+
* provider values like `session` and `cache` appear as properties
|
|
556
|
+
* without hard-coding the keys.
|
|
557
|
+
*/
|
|
558
|
+
defineProviderGetters(target) {
|
|
559
|
+
if (!this.#providers) return;
|
|
560
|
+
const state = this;
|
|
561
|
+
for (const key of this.#providers.keys()) {
|
|
562
|
+
Object.defineProperty(target, key, {
|
|
563
|
+
get: () => state.resolve(key),
|
|
564
|
+
enumerable: true,
|
|
565
|
+
configurable: true
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Resolves the route to use for this request and stores it on
|
|
571
|
+
* `this.routeData`. If the adapter (or the dev server) provided a
|
|
572
|
+
* `routeData` via render options it's already set and this is a
|
|
573
|
+
* no-op. Otherwise we use the app's synchronous route matcher and
|
|
574
|
+
* fall back to a `404.astro` route so middleware can still run.
|
|
575
|
+
*
|
|
576
|
+
* Called eagerly from the constructor so individual handlers
|
|
577
|
+
* (actions, pages, middleware, etc.) always see a resolved route
|
|
578
|
+
* without the caller needing an extra setup step.
|
|
579
|
+
*
|
|
580
|
+
* Once routeData is known, finalizes `this.pathname`: in dev, if the
|
|
581
|
+
* matched route has no `.html` extension, strip `.html` / `/index.html`
|
|
582
|
+
* suffixes so the rendering pipeline sees the canonical pathname.
|
|
583
|
+
*/
|
|
584
|
+
/**
|
|
585
|
+
* Strip `.html` / `/index.html` suffixes from the pathname so the
|
|
586
|
+
* rendering pipeline sees the canonical route path. Skipped when the
|
|
587
|
+
* matched route itself has an `.html` extension in its definition.
|
|
588
|
+
*/
|
|
589
|
+
#stripHtmlExtension() {
|
|
590
|
+
if (this.routeData && !routeHasHtmlExtension(this.routeData)) {
|
|
591
|
+
this.pathname = this.pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
#resolveRouteData() {
|
|
595
|
+
const pipeline = this.pipeline;
|
|
596
|
+
if (this.routeData) {
|
|
597
|
+
this.#stripHtmlExtension();
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const matched = pipeline.matchRoute(this.pathname);
|
|
601
|
+
if (matched && matched.prerender && pipeline.manifest.serverLike) {
|
|
602
|
+
this.routeData = void 0;
|
|
603
|
+
} else {
|
|
604
|
+
this.routeData = matched;
|
|
605
|
+
}
|
|
606
|
+
pipeline.logger.debug("router", "Astro matched the following route for " + this.request.url);
|
|
607
|
+
pipeline.logger.debug("router", "RouteData:\n" + this.routeData);
|
|
608
|
+
if (!this.routeData) {
|
|
609
|
+
this.routeData = pipeline.manifestData.routes.find(
|
|
610
|
+
(route) => route.component === "404.astro" || route.component === DEFAULT_404_COMPONENT
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
if (!this.routeData) {
|
|
614
|
+
pipeline.logger.debug("router", "Astro hasn't found routes that match " + this.request.url);
|
|
615
|
+
pipeline.logger.debug("router", "Here's the available routes:\n", pipeline.manifestData);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
this.#stripHtmlExtension();
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Strips the pipeline's base from the request URL, prepends a forward
|
|
622
|
+
* slash, and decodes the pathname. Falls back to the raw (not decoded)
|
|
623
|
+
* pathname if `decodeURI` throws.
|
|
624
|
+
*
|
|
625
|
+
* Mirrors `BaseApp.removeBase`, including the
|
|
626
|
+
* `collapseDuplicateLeadingSlashes` fix that prevents middleware
|
|
627
|
+
* authorization bypass when the URL starts with `//`.
|
|
628
|
+
*/
|
|
629
|
+
#computePathname(url) {
|
|
630
|
+
let pathname = collapseDuplicateLeadingSlashes(url.pathname);
|
|
631
|
+
const base = this.pipeline.manifest.base;
|
|
632
|
+
if (pathname.startsWith(base)) {
|
|
633
|
+
const baseWithoutTrailingSlash = removeTrailingForwardSlash(base);
|
|
634
|
+
pathname = pathname.slice(baseWithoutTrailingSlash.length + 1);
|
|
635
|
+
}
|
|
636
|
+
pathname = prependForwardSlash(pathname);
|
|
637
|
+
try {
|
|
638
|
+
return decodeURI(pathname);
|
|
639
|
+
} catch (e) {
|
|
640
|
+
this.pipeline.logger.error(null, e.toString());
|
|
641
|
+
return pathname;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Returns the resolved `props` for this render, computing them lazily
|
|
646
|
+
* from the route + component module on first access. If the
|
|
647
|
+
* `initialProps` already carries user-supplied props (e.g. the
|
|
648
|
+
* container API) those are used verbatim.
|
|
649
|
+
*/
|
|
650
|
+
async getProps() {
|
|
651
|
+
if (this.props !== null) return this.props;
|
|
652
|
+
if (Object.keys(this.initialProps).length > 0) {
|
|
653
|
+
this.props = this.initialProps;
|
|
654
|
+
return this.props;
|
|
655
|
+
}
|
|
656
|
+
const pipeline = this.pipeline;
|
|
657
|
+
const mod = await this.loadComponentInstance();
|
|
658
|
+
this.props = await getProps({
|
|
659
|
+
mod,
|
|
660
|
+
routeData: this.routeData,
|
|
661
|
+
routeCache: pipeline.routeCache,
|
|
662
|
+
pathname: this.pathname,
|
|
663
|
+
logger: pipeline.logger,
|
|
664
|
+
serverLike: pipeline.manifest.serverLike,
|
|
665
|
+
base: pipeline.manifest.base,
|
|
666
|
+
trailingSlash: pipeline.manifest.trailingSlash
|
|
667
|
+
});
|
|
668
|
+
return this.props;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Returns the `ActionAPIContext` for this render, creating it lazily.
|
|
672
|
+
* Used by middleware, actions, and page dispatch.
|
|
673
|
+
*/
|
|
674
|
+
getActionAPIContext() {
|
|
675
|
+
if (this.actionApiContext !== null) return this.actionApiContext;
|
|
676
|
+
const state = this;
|
|
677
|
+
const ctx = {
|
|
678
|
+
get cookies() {
|
|
679
|
+
return state.cookies;
|
|
680
|
+
},
|
|
681
|
+
routePattern: this.routeData.route,
|
|
682
|
+
isPrerendered: this.routeData.prerender,
|
|
683
|
+
get clientAddress() {
|
|
684
|
+
return state.getClientAddress();
|
|
685
|
+
},
|
|
686
|
+
get currentLocale() {
|
|
687
|
+
return state.computeCurrentLocale();
|
|
688
|
+
},
|
|
689
|
+
generator: ASTRO_GENERATOR,
|
|
690
|
+
get locals() {
|
|
691
|
+
return state.locals;
|
|
692
|
+
},
|
|
693
|
+
set locals(_) {
|
|
694
|
+
throw new AstroError(AstroErrorData.LocalsReassigned);
|
|
695
|
+
},
|
|
696
|
+
// SAFETY: getActionAPIContext is only called after route resolution,
|
|
697
|
+
// so routeData is always set and the params getter always returns a value.
|
|
698
|
+
params: this.params,
|
|
699
|
+
get preferredLocale() {
|
|
700
|
+
return state.computePreferredLocale();
|
|
701
|
+
},
|
|
702
|
+
get preferredLocaleList() {
|
|
703
|
+
return state.computePreferredLocaleList();
|
|
704
|
+
},
|
|
705
|
+
request: this.request,
|
|
706
|
+
site: this.pipeline.site,
|
|
707
|
+
url: this.url,
|
|
708
|
+
get originPathname() {
|
|
709
|
+
return getOriginPathname(state.request);
|
|
710
|
+
},
|
|
711
|
+
get csp() {
|
|
712
|
+
return state.getCsp();
|
|
713
|
+
},
|
|
714
|
+
get logger() {
|
|
715
|
+
if (!state.pipeline.manifest.experimentalLogger) {
|
|
716
|
+
state.pipeline.logger.warn(
|
|
717
|
+
null,
|
|
718
|
+
"The Astro.logger is available only when experimental.logger is defined."
|
|
719
|
+
);
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
info(msg) {
|
|
724
|
+
state.pipeline.logger.info(null, msg);
|
|
725
|
+
},
|
|
726
|
+
warn(msg) {
|
|
727
|
+
state.pipeline.logger.warn(null, msg);
|
|
728
|
+
},
|
|
729
|
+
error(msg) {
|
|
730
|
+
state.pipeline.logger.error(null, msg);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
this.defineProviderGetters(ctx);
|
|
736
|
+
this.actionApiContext = ctx;
|
|
737
|
+
return this.actionApiContext;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Returns the `APIContext` for this render, creating it lazily from
|
|
741
|
+
* the memoized props + action context.
|
|
742
|
+
*
|
|
743
|
+
* Callers must ensure `getProps()` has resolved at least once before
|
|
744
|
+
* calling this.
|
|
745
|
+
*/
|
|
746
|
+
getAPIContext() {
|
|
747
|
+
if (this.apiContext !== null) return this.apiContext;
|
|
748
|
+
const actionApiContext = this.getActionAPIContext();
|
|
749
|
+
const state = this;
|
|
750
|
+
const redirect = (path, status = 302) => new Response(null, { status, headers: { Location: path } });
|
|
751
|
+
const rewrite = async (reroutePayload) => {
|
|
752
|
+
return await state.rewrite(reroutePayload);
|
|
753
|
+
};
|
|
754
|
+
Reflect.set(actionApiContext, pipelineSymbol, this.pipeline);
|
|
755
|
+
actionApiContext[fetchStateSymbol] = this;
|
|
756
|
+
this.apiContext = Object.assign(actionApiContext, {
|
|
757
|
+
props: this.props,
|
|
758
|
+
redirect,
|
|
759
|
+
rewrite,
|
|
760
|
+
getActionResult: createGetActionResult(actionApiContext.locals),
|
|
761
|
+
callAction: createCallAction(actionApiContext)
|
|
762
|
+
});
|
|
763
|
+
return this.apiContext;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Invalidates the cached `APIContext` so the next `getAPIContext()`
|
|
767
|
+
* call re-derives it from the (possibly mutated) state. Used
|
|
768
|
+
* after an in-flight rewrite swaps the route / request / params.
|
|
769
|
+
*/
|
|
770
|
+
invalidateContexts() {
|
|
771
|
+
this.props = null;
|
|
772
|
+
this.actionApiContext = null;
|
|
773
|
+
this.apiContext = null;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
export {
|
|
777
|
+
FetchState,
|
|
778
|
+
getFetchStateFromAPIContext
|
|
779
|
+
};
|