@astrojs/cloudflare 13.0.0-beta.0 → 13.0.0-beta.10

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 (39) hide show
  1. package/dist/entrypoints/{image-service.js → image-service-external.js} +2 -2
  2. package/dist/entrypoints/image-service-workerd.d.ts +14 -0
  3. package/dist/entrypoints/image-service-workerd.js +11 -0
  4. package/dist/entrypoints/image-transform-endpoint.js +21 -2
  5. package/dist/entrypoints/preview.js +4 -7
  6. package/dist/esbuild-plugin-astro-frontmatter.d.ts +9 -0
  7. package/dist/esbuild-plugin-astro-frontmatter.js +30 -0
  8. package/dist/index.d.ts +7 -57
  9. package/dist/index.js +108 -79
  10. package/dist/info.d.ts +5 -0
  11. package/dist/info.js +4 -0
  12. package/dist/prerender-types.d.ts +32 -0
  13. package/dist/prerender-types.js +0 -0
  14. package/dist/prerenderer.d.ts +17 -0
  15. package/dist/prerenderer.js +116 -0
  16. package/dist/utils/generate-routes-json.d.ts +1 -8
  17. package/dist/utils/generate-routes-json.js +0 -203
  18. package/dist/utils/handler.d.ts +3 -8
  19. package/dist/utils/handler.js +40 -18
  20. package/dist/utils/image-binding-transform.d.ts +1 -2
  21. package/dist/utils/image-binding-transform.js +14 -6
  22. package/dist/utils/image-config.d.ts +19 -6
  23. package/dist/utils/image-config.js +30 -8
  24. package/dist/utils/prerender-constants.d.ts +9 -0
  25. package/dist/utils/prerender-constants.js +8 -0
  26. package/dist/utils/prerender.d.ts +37 -0
  27. package/dist/utils/prerender.js +76 -0
  28. package/dist/utils/static-image-collection.d.ts +7 -0
  29. package/dist/utils/static-image-collection.js +51 -0
  30. package/dist/vite-plugin-config.d.ts +10 -3
  31. package/dist/vite-plugin-config.js +5 -2
  32. package/dist/wrangler.d.ts +3 -3
  33. package/package.json +18 -18
  34. package/types.d.ts +3 -18
  35. package/dist/entrypoints/image-endpoint.d.ts +0 -3
  36. package/dist/entrypoints/image-endpoint.js +0 -29
  37. package/dist/utils/cloudflare-module-loader.d.ts +0 -16
  38. package/dist/utils/cloudflare-module-loader.js +0 -152
  39. /package/dist/entrypoints/{image-service.d.ts → image-service-external.d.ts} +0 -0
@@ -0,0 +1,116 @@
1
+ import { preview } from "vite";
2
+ import { fileURLToPath } from "node:url";
3
+ import { mkdir } from "node:fs/promises";
4
+ import { cloudflare as cfVitePlugin } from "@cloudflare/vite-plugin";
5
+ import { serializeRouteData, deserializeRouteData } from "astro/app/manifest";
6
+ import {
7
+ STATIC_PATHS_ENDPOINT,
8
+ PRERENDER_ENDPOINT,
9
+ STATIC_IMAGES_ENDPOINT
10
+ } from "./utils/prerender-constants.js";
11
+ function createCloudflarePrerenderer({
12
+ root,
13
+ serverDir,
14
+ clientDir,
15
+ base,
16
+ trailingSlash,
17
+ cfPluginConfig,
18
+ hasCompileImageService
19
+ }) {
20
+ let previewServer;
21
+ let serverUrl;
22
+ return {
23
+ name: "@astrojs/cloudflare:prerenderer",
24
+ async setup() {
25
+ await mkdir(clientDir, { recursive: true });
26
+ previewServer = await preview({
27
+ configFile: false,
28
+ base,
29
+ appType: "mpa",
30
+ build: {
31
+ outDir: fileURLToPath(serverDir)
32
+ },
33
+ root: fileURLToPath(root),
34
+ preview: {
35
+ host: "localhost",
36
+ port: 0,
37
+ // Let the OS pick a free port
38
+ open: false
39
+ },
40
+ plugins: [cfVitePlugin({ ...cfPluginConfig, viteEnvironment: { name: "prerender" } })]
41
+ });
42
+ const address = previewServer.httpServer.address();
43
+ if (address && typeof address === "object") {
44
+ serverUrl = `http://localhost:${address.port}`;
45
+ } else {
46
+ throw new Error(
47
+ "Failed to start the Cloudflare prerender server. The preview server did not return a valid address. This is likely a bug in @astrojs/cloudflare. Please file an issue at https://github.com/withastro/astro/issues"
48
+ );
49
+ }
50
+ },
51
+ async getStaticPaths() {
52
+ const response = await fetch(`${serverUrl}${STATIC_PATHS_ENDPOINT}`, {
53
+ method: "POST",
54
+ headers: { "Content-Type": "application/json" }
55
+ });
56
+ if (!response.ok) {
57
+ throw new Error(
58
+ `Failed to get static paths from the Cloudflare prerender server (${response.status}: ${response.statusText}). This is likely a bug in @astrojs/cloudflare. Please file an issue at https://github.com/withastro/astro/issues`
59
+ );
60
+ }
61
+ const data = await response.json();
62
+ return data.paths.map(({ pathname, route }) => ({
63
+ pathname,
64
+ route: deserializeRouteData(route)
65
+ }));
66
+ },
67
+ async render(request, { routeData }) {
68
+ const body = {
69
+ url: request.url,
70
+ routeData: serializeRouteData(routeData, trailingSlash)
71
+ };
72
+ const response = await fetch(`${serverUrl}${PRERENDER_ENDPOINT}`, {
73
+ method: "POST",
74
+ headers: { "Content-Type": "application/json" },
75
+ body: JSON.stringify(body)
76
+ });
77
+ return response;
78
+ },
79
+ collectStaticImages: hasCompileImageService ? async () => {
80
+ const response = await fetch(`${serverUrl}${STATIC_IMAGES_ENDPOINT}`, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" }
83
+ });
84
+ if (!response.ok) {
85
+ throw new Error(
86
+ `Failed to get static images from the Cloudflare prerender server (${response.status}: ${response.statusText}).`
87
+ );
88
+ }
89
+ const entries = await response.json();
90
+ const { default: sharpService } = await import("astro/assets/services/sharp");
91
+ globalThis.astroAsset ??= {};
92
+ globalThis.astroAsset.imageService = sharpService;
93
+ const staticImages = /* @__PURE__ */ new Map();
94
+ for (const entry of entries) {
95
+ const transforms = /* @__PURE__ */ new Map();
96
+ for (const t of entry.transforms) {
97
+ transforms.set(t.hash, { finalPath: t.finalPath, transform: t.transform });
98
+ }
99
+ staticImages.set(entry.originalPath, {
100
+ originalSrcPath: entry.originalSrcPath,
101
+ transforms
102
+ });
103
+ }
104
+ return staticImages;
105
+ } : void 0,
106
+ async teardown() {
107
+ if (previewServer) {
108
+ await previewServer.close();
109
+ previewServer = void 0;
110
+ }
111
+ }
112
+ };
113
+ }
114
+ export {
115
+ createCloudflarePrerenderer
116
+ };
@@ -1,9 +1,2 @@
1
- import type { AstroConfig, AstroIntegrationLogger, IntegrationResolvedRoute, RoutePart } from 'astro';
1
+ import type { RoutePart } from 'astro';
2
2
  export declare function getParts(part: string): RoutePart[];
3
- export declare function createRoutesFile(_config: AstroConfig, logger: AstroIntegrationLogger, routes: IntegrationResolvedRoute[], pages: {
4
- pathname: string;
5
- }[], redirects: IntegrationResolvedRoute['segments'][], includeExtends: {
6
- pattern: string;
7
- }[] | undefined, excludeExtends: {
8
- pattern: string;
9
- }[] | undefined): Promise<void>;
@@ -1,13 +1,3 @@
1
- import { existsSync } from "node:fs";
2
- import { writeFile } from "node:fs/promises";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import {
6
- prependForwardSlash,
7
- removeLeadingForwardSlash,
8
- removeTrailingForwardSlash
9
- } from "@astrojs/internal-helpers/path";
10
- import { glob } from "tinyglobby";
11
1
  const ROUTE_DYNAMIC_SPLIT = /\[(.+?\(.+?\)|.+?)\]/;
12
2
  const ROUTE_SPREAD = /^\.{3}.+$/;
13
3
  function getParts(part) {
@@ -27,199 +17,6 @@ function getParts(part) {
27
17
  });
28
18
  return result;
29
19
  }
30
- async function writeRoutesFileToOutDir(_config, logger, include, exclude) {
31
- try {
32
- await writeFile(
33
- new URL("./_routes.json", _config.outDir),
34
- JSON.stringify(
35
- {
36
- version: 1,
37
- include,
38
- exclude
39
- },
40
- null,
41
- 2
42
- ),
43
- "utf-8"
44
- );
45
- } catch (_error) {
46
- logger.error("There was an error writing the '_routes.json' file to the output directory.");
47
- }
48
- }
49
- function segmentsToCfSyntax(segments, _config) {
50
- const pathSegments = [];
51
- if (removeLeadingForwardSlash(removeTrailingForwardSlash(_config.base)).length > 0) {
52
- pathSegments.push(removeLeadingForwardSlash(removeTrailingForwardSlash(_config.base)));
53
- }
54
- for (const segment of segments.flat()) {
55
- if (segment.dynamic) pathSegments.push("*");
56
- else pathSegments.push(segment.content);
57
- }
58
- return pathSegments;
59
- }
60
- class TrieNode {
61
- children = /* @__PURE__ */ new Map();
62
- isEndOfPath = false;
63
- hasWildcardChild = false;
64
- }
65
- class PathTrie {
66
- root;
67
- returnHasWildcard = false;
68
- constructor() {
69
- this.root = new TrieNode();
70
- }
71
- insert(thisPath) {
72
- let node = this.root;
73
- for (const segment of thisPath) {
74
- if (segment === "*") {
75
- node.hasWildcardChild = true;
76
- break;
77
- }
78
- if (!node.children.has(segment)) {
79
- node.children.set(segment, new TrieNode());
80
- }
81
- node = node.children.get(segment);
82
- }
83
- node.isEndOfPath = true;
84
- }
85
- /**
86
- * Depth-first search (dfs), traverses the "graph" segment by segment until the end or wildcard (*).
87
- * It makes sure that all necessary paths are returned, but not paths with an existing wildcard prefix.
88
- * e.g. if we have a path like /foo/* and /foo/bar, we only want to return /foo/*
89
- */
90
- dfs(node, thisPath, allPaths) {
91
- if (node.hasWildcardChild) {
92
- this.returnHasWildcard = true;
93
- allPaths.push([...thisPath, "*"]);
94
- return;
95
- }
96
- if (node.isEndOfPath) {
97
- allPaths.push([...thisPath]);
98
- }
99
- for (const [segment, childNode] of node.children) {
100
- this.dfs(childNode, [...thisPath, segment], allPaths);
101
- }
102
- }
103
- /**
104
- * The reduce function is used to remove unnecessary paths from the trie.
105
- * It receives a trie node to compare with the current node.
106
- */
107
- reduce(compNode, node) {
108
- if (node.hasWildcardChild || compNode.hasWildcardChild) return;
109
- for (const [segment, childNode] of node.children) {
110
- if (childNode.children.size === 0) continue;
111
- const compChildNode = compNode.children.get(segment);
112
- if (compChildNode === void 0) {
113
- childNode.hasWildcardChild = true;
114
- continue;
115
- }
116
- this.reduce(compChildNode, childNode);
117
- }
118
- }
119
- reduceAllPaths(compTrie) {
120
- this.reduce(compTrie.root, this.root);
121
- return this;
122
- }
123
- getAllPaths() {
124
- const allPaths = [];
125
- this.dfs(this.root, [], allPaths);
126
- return [allPaths, this.returnHasWildcard];
127
- }
128
- }
129
- async function createRoutesFile(_config, logger, routes, pages, redirects, includeExtends, excludeExtends) {
130
- const includePaths = [];
131
- const excludePaths = [];
132
- const assetsPath = segmentsToCfSyntax(
133
- [
134
- [{ content: _config.build.assets, dynamic: false, spread: false }],
135
- [{ content: "", dynamic: true, spread: false }]
136
- ],
137
- _config
138
- );
139
- excludePaths.push(assetsPath);
140
- for (const redirect of redirects) {
141
- excludePaths.push(segmentsToCfSyntax(redirect, _config));
142
- }
143
- if (existsSync(fileURLToPath(_config.publicDir))) {
144
- const staticFiles = await glob(`**/*`, {
145
- cwd: fileURLToPath(_config.publicDir),
146
- dot: true
147
- });
148
- for (const staticFile of staticFiles) {
149
- if (["_headers", "_redirects", "_routes.json"].includes(staticFile)) continue;
150
- const staticPath = staticFile;
151
- const segments = removeLeadingForwardSlash(staticPath).split(path.sep).filter(Boolean).map((s) => {
152
- return getParts(s);
153
- });
154
- excludePaths.push(segmentsToCfSyntax(segments, _config));
155
- }
156
- }
157
- let hasPrerendered404 = false;
158
- for (const route of routes) {
159
- const convertedPath = segmentsToCfSyntax(route.segments, _config);
160
- if (route.pathname === "/404" && route.isPrerendered === true) hasPrerendered404 = true;
161
- switch (route.type) {
162
- case "page":
163
- if (route.isPrerendered === false) includePaths.push(convertedPath);
164
- break;
165
- case "endpoint":
166
- if (route.isPrerendered === false) includePaths.push(convertedPath);
167
- else excludePaths.push(convertedPath);
168
- break;
169
- case "redirect":
170
- excludePaths.push(convertedPath);
171
- break;
172
- default:
173
- includePaths.push(convertedPath);
174
- break;
175
- }
176
- }
177
- for (const page of pages) {
178
- if (page.pathname === "404") hasPrerendered404 = true;
179
- const pageSegments = removeLeadingForwardSlash(page.pathname).split(path.posix.sep).filter(Boolean).map((s) => {
180
- return getParts(s);
181
- });
182
- excludePaths.push(segmentsToCfSyntax(pageSegments, _config));
183
- }
184
- const includeTrie = new PathTrie();
185
- for (const includePath of includePaths) {
186
- includeTrie.insert(includePath);
187
- }
188
- const excludeTrie = new PathTrie();
189
- for (const excludePath of excludePaths) {
190
- if (excludePath[0] === "*") continue;
191
- excludeTrie.insert(excludePath);
192
- }
193
- const [deduplicatedIncludePaths, includedPathsHaveWildcard] = includeTrie.reduceAllPaths(excludeTrie).getAllPaths();
194
- const [deduplicatedExcludePaths, _excludedPathsHaveWildcard] = excludeTrie.reduceAllPaths(includeTrie).getAllPaths();
195
- const CLOUDFLARE_COMBINED_LIMIT = 100;
196
- const AUTOMATIC_INCLUDE_RULES_COUNT = deduplicatedIncludePaths.length;
197
- const EXTENDED_INCLUDE_RULES_COUNT = includeExtends?.length ?? 0;
198
- const INCLUDE_RULES_COUNT = AUTOMATIC_INCLUDE_RULES_COUNT + EXTENDED_INCLUDE_RULES_COUNT;
199
- const AUTOMATIC_EXCLUDE_RULES_COUNT = deduplicatedExcludePaths.length;
200
- const EXTENDED_EXCLUDE_RULES_COUNT = excludeExtends?.length ?? 0;
201
- const EXCLUDE_RULES_COUNT = AUTOMATIC_EXCLUDE_RULES_COUNT + EXTENDED_EXCLUDE_RULES_COUNT;
202
- const OPTION2_TOTAL_COUNT = INCLUDE_RULES_COUNT + (includedPathsHaveWildcard ? EXCLUDE_RULES_COUNT : 0);
203
- if (!hasPrerendered404 || OPTION2_TOTAL_COUNT > CLOUDFLARE_COMBINED_LIMIT) {
204
- await writeRoutesFileToOutDir(
205
- _config,
206
- logger,
207
- ["/*"].concat(includeExtends?.map((entry) => entry.pattern) ?? []),
208
- deduplicatedExcludePaths.map((thisPath) => `${prependForwardSlash(thisPath.join("/"))}`).slice(
209
- 0,
210
- CLOUDFLARE_COMBINED_LIMIT - EXTENDED_INCLUDE_RULES_COUNT - EXTENDED_EXCLUDE_RULES_COUNT - 1
211
- ).concat(excludeExtends?.map((entry) => entry.pattern) ?? [])
212
- );
213
- } else {
214
- await writeRoutesFileToOutDir(
215
- _config,
216
- logger,
217
- deduplicatedIncludePaths.map((thisPath) => `${prependForwardSlash(thisPath.join("/"))}`).concat(includeExtends?.map((entry) => entry.pattern) ?? []),
218
- includedPathsHaveWildcard ? deduplicatedExcludePaths.map((thisPath) => `${prependForwardSlash(thisPath.join("/"))}`).concat(excludeExtends?.map((entry) => entry.pattern) ?? []) : []
219
- );
220
- }
221
- }
222
20
  export {
223
- createRoutesFile,
224
21
  getParts
225
22
  };
@@ -1,14 +1,9 @@
1
- import type { Response as CfResponse, ExecutionContext, ExportedHandlerFetchHandler } from '@cloudflare/workers-types';
2
- export type Env = {
3
- [key: string]: unknown;
4
- ASSETS: {
5
- fetch: (req: Request | string) => Promise<CfResponse>;
6
- };
7
- };
8
1
  export interface Runtime {
9
2
  cfContext: ExecutionContext;
10
3
  }
11
4
  declare global {
12
5
  var __ASTRO_IMAGES_BINDING_NAME: string;
13
6
  }
14
- export declare function handle(request: Parameters<ExportedHandlerFetchHandler>[0], env: Env, context: ExecutionContext): Promise<CfResponse>;
7
+ type CfResponse = Awaited<ReturnType<Required<ExportedHandler<Env>>['fetch']>>;
8
+ export declare function handle(request: Request, env: Env, context: ExecutionContext): Promise<CfResponse>;
9
+ export {};
@@ -1,26 +1,51 @@
1
1
  import { env as globalEnv } from "cloudflare:workers";
2
- import { sessionKVBindingName } from "virtual:astro-cloudflare:config";
2
+ import {
3
+ sessionKVBindingName,
4
+ compileImageConfig,
5
+ isPrerender
6
+ } from "virtual:astro-cloudflare:config";
3
7
  import { createApp } from "astro/app/entrypoint";
4
8
  import { setGetEnv } from "astro/env/setup";
5
9
  import { createGetEnv } from "../utils/env.js";
10
+ import {
11
+ isStaticPathsRequest,
12
+ isPrerenderRequest,
13
+ handleStaticPathsRequest,
14
+ handlePrerenderRequest,
15
+ isStaticImagesRequest,
16
+ handleStaticImagesRequest
17
+ } from "./prerender.js";
6
18
  setGetEnv(createGetEnv(globalEnv));
19
+ const app = createApp();
7
20
  async function handle(request, env, context) {
8
- const app = createApp(import.meta.env.DEV);
9
- const { pathname } = new URL(request.url);
21
+ if (isPrerender) {
22
+ if (compileImageConfig) {
23
+ const { installAddStaticImage } = await import("./static-image-collection.js");
24
+ installAddStaticImage(compileImageConfig);
25
+ }
26
+ if (isStaticPathsRequest(request)) {
27
+ return handleStaticPathsRequest(app);
28
+ }
29
+ if (isPrerenderRequest(request)) {
30
+ return handlePrerenderRequest(app, request);
31
+ }
32
+ if (isStaticImagesRequest(request)) {
33
+ return handleStaticImagesRequest();
34
+ }
35
+ }
36
+ const { pathname: requestPathname } = new URL(request.url);
10
37
  if (env[sessionKVBindingName]) {
11
38
  const sessionConfigOptions = app.manifest.sessionConfig?.options ?? {};
12
39
  Object.assign(sessionConfigOptions, {
13
40
  binding: env[sessionKVBindingName]
14
41
  });
15
42
  }
16
- if (app.manifest.assets.has(pathname)) {
43
+ if (app.manifest.assets.has(requestPathname)) {
17
44
  return env.ASSETS.fetch(request.url.replace(/\.html$/, ""));
18
45
  }
19
46
  let routeData = void 0;
20
47
  if (app.isDev()) {
21
- const result = await app.devMatch(
22
- app.getPathnameFromRequest(request)
23
- );
48
+ const result = await app.devMatch(app.getPathnameFromRequest(request));
24
49
  if (result) {
25
50
  routeData = result.routeData;
26
51
  }
@@ -63,17 +88,14 @@ async function handle(request, env, context) {
63
88
  }
64
89
  }
65
90
  });
66
- const response = await app.render(
67
- request,
68
- {
69
- routeData,
70
- locals,
71
- prerenderedErrorPageFetch: async (url) => {
72
- return env.ASSETS.fetch(url.replace(/\.html$/, ""));
73
- },
74
- clientAddress: request.headers.get("cf-connecting-ip") ?? void 0
75
- }
76
- );
91
+ const response = await app.render(request, {
92
+ routeData,
93
+ locals,
94
+ prerenderedErrorPageFetch: async (url) => {
95
+ return env.ASSETS.fetch(url.replace(/\.html$/, ""));
96
+ },
97
+ clientAddress: request.headers.get("cf-connecting-ip") ?? void 0
98
+ });
77
99
  if (app.setCookieHeaders) {
78
100
  for (const setCookieHeader of app.setCookieHeaders(response)) {
79
101
  response.headers.append("Set-Cookie", setCookieHeader);
@@ -1,2 +1 @@
1
- import type { Fetcher, ImagesBinding } from '@cloudflare/workers-types';
2
- export declare function transform(rawUrl: string, images: ImagesBinding, assets: Fetcher): Promise<import("@cloudflare/workers-types").Response | Response>;
1
+ export declare function transform(rawUrl: string, images: ImagesBinding, assets: Fetcher): Promise<Response>;
@@ -13,17 +13,25 @@ async function transform(rawUrl, images, assets) {
13
13
  return new Response(null, { status: 404 });
14
14
  }
15
15
  const input = images.input(content.body);
16
- const format = url.searchParams.get("f");
17
- if (!format || !["avif", "webp", "jpeg"].includes(format)) {
18
- return new Response(`The "${format}" format is not supported`, { status: 400 });
16
+ const supportedFormats = {
17
+ jpeg: "image/jpeg",
18
+ jpg: "image/jpeg",
19
+ png: "image/png",
20
+ gif: "image/gif",
21
+ webp: "image/webp",
22
+ avif: "image/avif"
23
+ };
24
+ const outputFormat = supportedFormats[url.searchParams.get("f") ?? ""];
25
+ if (!outputFormat) {
26
+ return new Response(`Unsupported format: ${url.searchParams.get("f")}`, { status: 400 });
19
27
  }
20
28
  return (await input.transform({
21
- width: url.searchParams.has("w") ? parseInt(url.searchParams.get("w")) : void 0,
22
- height: url.searchParams.has("h") ? parseInt(url.searchParams.get("h")) : void 0,
29
+ width: url.searchParams.has("w") ? Number.parseInt(url.searchParams.get("w")) : void 0,
30
+ height: url.searchParams.has("h") ? Number.parseInt(url.searchParams.get("h")) : void 0,
23
31
  // `quality` is documented, but doesn't appear to work in manual testing...
24
32
  // quality: url.searchParams.get('q'),
25
33
  fit: url.searchParams.get("fit")
26
- }).output({ format: `image/${format}` })).response();
34
+ }).output({ format: outputFormat })).response();
27
35
  }
28
36
  export {
29
37
  transform
@@ -1,8 +1,19 @@
1
1
  import type { AstroConfig, AstroIntegrationLogger, HookParameters } from 'astro';
2
- export type ImageService = 'passthrough' | 'cloudflare' | 'cloudflare-binding' | 'compile' | 'custom';
3
- export declare function setImageConfig(service: ImageService, config: AstroConfig['image'], command: HookParameters<'astro:config:setup'>['command'], logger: AstroIntegrationLogger): {
2
+ export type ImageServiceMode = 'passthrough' | 'cloudflare' | 'cloudflare-binding' | 'compile' | 'custom';
3
+ export type ImageServiceConfig = ImageServiceMode | {
4
+ build: 'compile';
5
+ runtime?: 'passthrough' | 'cloudflare-binding';
6
+ };
7
+ /** Normalize string | compound config into separate build/runtime modes. */
8
+ export declare function normalizeImageServiceConfig(config: ImageServiceConfig | undefined): {
9
+ buildService: ImageServiceMode;
10
+ runtimeService: ImageServiceMode;
11
+ };
12
+ export declare function setImageConfig(service: ImageServiceConfig | undefined, config: AstroConfig['image'], command: HookParameters<'astro:config:setup'>['command'], logger: AstroIntegrationLogger): {
4
13
  service: import("astro").ImageServiceConfig<Record<string, any>>;
5
14
  endpoint: {
15
+ entrypoint: string;
16
+ } | {
6
17
  route: string;
7
18
  entrypoint?: string | undefined;
8
19
  };
@@ -14,14 +25,16 @@ export declare function setImageConfig(service: ImageService, config: AstroConfi
14
25
  pathname?: string | undefined;
15
26
  }[];
16
27
  responsiveStyles: boolean;
17
- layout?: "none" | "fixed" | "constrained" | "full-width" | undefined;
28
+ layout?: "fixed" | "none" | "constrained" | "full-width" | undefined;
18
29
  objectFit?: string | undefined;
19
30
  objectPosition?: string | undefined;
20
31
  breakpoints?: number[] | undefined;
21
32
  } | {
22
- service: import("astro").ImageServiceConfig<Record<string, any>>;
33
+ service: {
34
+ entrypoint: string;
35
+ };
23
36
  endpoint: {
24
- entrypoint: string | undefined;
37
+ entrypoint: string;
25
38
  };
26
39
  domains: string[];
27
40
  remotePatterns: {
@@ -31,7 +44,7 @@ export declare function setImageConfig(service: ImageService, config: AstroConfi
31
44
  pathname?: string | undefined;
32
45
  }[];
33
46
  responsiveStyles: boolean;
34
- layout?: "none" | "fixed" | "constrained" | "full-width" | undefined;
47
+ layout?: "fixed" | "none" | "constrained" | "full-width" | undefined;
35
48
  objectFit?: string | undefined;
36
49
  objectPosition?: string | undefined;
37
50
  breakpoints?: number[] | undefined;
@@ -1,16 +1,37 @@
1
- import { passthroughImageService, sharpImageService } from "astro/config";
1
+ import { passthroughImageService } from "astro/config";
2
+ function normalizeImageServiceConfig(config) {
3
+ if (!config || typeof config === "string") {
4
+ const mode = config ?? "cloudflare-binding";
5
+ return {
6
+ buildService: mode,
7
+ runtimeService: mode === "compile" ? "passthrough" : mode
8
+ };
9
+ }
10
+ return {
11
+ buildService: "compile",
12
+ runtimeService: config.runtime ?? "passthrough"
13
+ };
14
+ }
15
+ const GENERIC_ENDPOINT = { entrypoint: "astro/assets/endpoint/generic" };
16
+ const WORKERD_IMAGE_SERVICE = { entrypoint: "@astrojs/cloudflare/image-service-workerd" };
2
17
  function setImageConfig(service, config, command, logger) {
3
- switch (service) {
18
+ const { buildService, runtimeService } = normalizeImageServiceConfig(service);
19
+ switch (buildService) {
4
20
  case "passthrough":
5
- return { ...config, service: passthroughImageService() };
21
+ return {
22
+ ...config,
23
+ service: passthroughImageService(),
24
+ endpoint: command === "dev" ? GENERIC_ENDPOINT : config.endpoint
25
+ };
6
26
  case "cloudflare":
7
27
  return {
8
28
  ...config,
9
- service: command === "dev" ? sharpImageService() : { entrypoint: "@astrojs/cloudflare/image-service" }
29
+ service: { entrypoint: "@astrojs/cloudflare/image-service" }
10
30
  };
11
31
  case "cloudflare-binding":
12
32
  return {
13
33
  ...config,
34
+ service: WORKERD_IMAGE_SERVICE,
14
35
  endpoint: {
15
36
  entrypoint: "@astrojs/cloudflare/image-transform-endpoint"
16
37
  }
@@ -18,10 +39,10 @@ function setImageConfig(service, config, command, logger) {
18
39
  case "compile":
19
40
  return {
20
41
  ...config,
21
- service: sharpImageService(),
22
- endpoint: {
23
- entrypoint: command === "dev" ? void 0 : "@astrojs/cloudflare/image-endpoint"
24
- }
42
+ service: WORKERD_IMAGE_SERVICE,
43
+ // Dev: IMAGES binding (via Cloudflare Vite plugin) for real transforms.
44
+ // Build: endpoint depends on runtime - `cloudflare-binding` uses IMAGES, `passthrough` uses generic.
45
+ endpoint: command === "dev" || runtimeService === "cloudflare-binding" ? { entrypoint: "@astrojs/cloudflare/image-transform-endpoint" } : GENERIC_ENDPOINT
25
46
  };
26
47
  case "custom":
27
48
  return { ...config };
@@ -36,5 +57,6 @@ function setImageConfig(service, config, command, logger) {
36
57
  }
37
58
  }
38
59
  export {
60
+ normalizeImageServiceConfig,
39
61
  setImageConfig
40
62
  };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Constants for prerender endpoints used by Cloudflare adapter
3
+ */
4
+ /** Internal endpoint for fetching all static paths during prerendering */
5
+ export declare const STATIC_PATHS_ENDPOINT = "/__astro_static_paths";
6
+ /** Internal endpoint for rendering a specific page during prerendering */
7
+ export declare const PRERENDER_ENDPOINT = "/__astro_prerender";
8
+ /** Internal endpoint for fetching static images collected in workerd during `compile` builds */
9
+ export declare const STATIC_IMAGES_ENDPOINT = "/__astro_static_images";
@@ -0,0 +1,8 @@
1
+ const STATIC_PATHS_ENDPOINT = "/__astro_static_paths";
2
+ const PRERENDER_ENDPOINT = "/__astro_prerender";
3
+ const STATIC_IMAGES_ENDPOINT = "/__astro_static_images";
4
+ export {
5
+ PRERENDER_ENDPOINT,
6
+ STATIC_IMAGES_ENDPOINT,
7
+ STATIC_PATHS_ENDPOINT
8
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Prerender utilities for Cloudflare adapter
3
+ *
4
+ * During the build process, Astro prerenders pages by making requests to internal endpoints
5
+ * served by the Cloudflare worker running in workerd. These endpoints are:
6
+ *
7
+ * - `/__astro_static_paths`: Returns all static paths that need to be prerendered.
8
+ * The prerenderer calls this to discover which routes/pages need to be generated.
9
+ *
10
+ * - `/__astro_prerender`: Renders a specific page given its URL and route data.
11
+ * The prerenderer calls this for each path to generate the static HTML.
12
+ *
13
+ * These endpoints are only active during the prerender build phase and are not
14
+ * available in production or development.
15
+ */
16
+ import type { BaseApp } from 'astro/app';
17
+ /**
18
+ * Checks if the request is for the static paths prerender endpoint.
19
+ * This endpoint returns all paths that need to be prerendered.
20
+ */
21
+ export declare function isStaticPathsRequest(request: Request): boolean;
22
+ /**
23
+ * Checks if the request is for the prerender endpoint.
24
+ * This endpoint renders a specific page during the prerender phase.
25
+ */
26
+ export declare function isPrerenderRequest(request: Request): boolean;
27
+ /**
28
+ * Handles the static paths request, returning all paths that need prerendering.
29
+ */
30
+ export declare function handleStaticPathsRequest(app: BaseApp): Promise<Response>;
31
+ /**
32
+ * Handles a prerender request, rendering the specified page.
33
+ */
34
+ export declare function handlePrerenderRequest(app: BaseApp, request: Request): Promise<Response>;
35
+ export declare function isStaticImagesRequest(request: Request): boolean;
36
+ /** Serializes the global staticImages map collected in workerd back to the Node-side build. */
37
+ export declare function handleStaticImagesRequest(): Response;