@astrojs/cloudflare 13.5.5 → 13.6.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.
@@ -41,7 +41,7 @@ const createPreviewServer = async ({
41
41
  allowedHosts
42
42
  },
43
43
  plugins: [
44
- cfVitePlugin({ ...globalThis.astroCloudflareOptions, viteEnvironment: { name: "ssr" } })
44
+ cfVitePlugin({ ...globalThis.astroCloudflareConfig, viteEnvironment: { name: "ssr" } })
45
45
  ]
46
46
  });
47
47
  } catch (err) {
@@ -88,7 +88,7 @@ function serverStart({
88
88
  host,
89
89
  base
90
90
  }) {
91
- const version = "13.5.5";
91
+ const version = "13.6.0";
92
92
  const localPrefix = `${colors.dim("\u2503")} Local `;
93
93
  const networkPrefix = `${colors.dim("\u2503")} Network `;
94
94
  const emptyPrefix = " ".repeat(11);
@@ -0,0 +1,12 @@
1
+ import type { FetchState } from 'astro/fetch';
2
+ /**
3
+ * Applies Cloudflare-specific setup to a `FetchState`:
4
+ * - Injects the SESSION KV binding
5
+ * - Serves static assets via the ASSETS binding
6
+ * - Sets `locals.cfContext`, client address, `waitUntil`, and error page fetch
7
+ *
8
+ * Returns a `Response` if the request was handled by the ASSETS binding
9
+ * (static file hit). Returns `undefined` when the caller should continue
10
+ * to Astro rendering.
11
+ */
12
+ export declare function cf(state: FetchState, env: Env, ctx: ExecutionContext): Promise<Response | undefined>;
package/dist/fetch.js ADDED
@@ -0,0 +1,31 @@
1
+ import { env as globalEnv } from "cloudflare:workers";
2
+ import { createApp } from "astro/app/entrypoint";
3
+ import { setGetEnv } from "astro/env/setup";
4
+ import { createGetEnv } from "./utils/env.js";
5
+ import {
6
+ injectSessionBinding,
7
+ matchStaticAsset,
8
+ fallbackToAssets,
9
+ createErrorPageFetch,
10
+ createLocals,
11
+ getClientAddress
12
+ } from "./utils/cf.js";
13
+ setGetEnv(createGetEnv(globalEnv));
14
+ const app = createApp();
15
+ async function cf(state, env, ctx) {
16
+ injectSessionBinding(app.manifest, env);
17
+ const staticAsset = matchStaticAsset(app.manifest, state.request.url, env);
18
+ if (staticAsset) return staticAsset;
19
+ if (!state.routeData) {
20
+ const asset = await fallbackToAssets(state.request.url, env);
21
+ if (asset) return asset;
22
+ }
23
+ Object.assign(state.locals, createLocals(ctx));
24
+ state.clientAddress = getClientAddress(state.request);
25
+ state.renderOptions.waitUntil = ctx.waitUntil.bind(ctx);
26
+ state.renderOptions.prerenderedErrorPageFetch = createErrorPageFetch(env);
27
+ return void 0;
28
+ }
29
+ export {
30
+ cf
31
+ };
package/dist/hono.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Duck-typed Hono context — matches Hono's `Context` shape for
3
+ * Cloudflare Workers without importing from `hono` at runtime.
4
+ */
5
+ type HonoCloudflareContextLike = {
6
+ req: {
7
+ raw: Request;
8
+ };
9
+ env: Env;
10
+ executionCtx: ExecutionContext;
11
+ get?: (key: string) => unknown;
12
+ set?: (key: string, value: unknown) => void;
13
+ };
14
+ type HonoMiddlewareHandler = (context: HonoCloudflareContextLike, next: () => Promise<void>) => Promise<Response | void>;
15
+ /**
16
+ * Hono middleware that applies Cloudflare-specific setup.
17
+ *
18
+ * Reads `env` and `executionCtx` from the Hono context (provided
19
+ * automatically by Hono on Cloudflare Workers). Handles static assets
20
+ * via the ASSETS binding, injects the SESSION KV binding, and sets
21
+ * `locals.cfContext`, client address, `waitUntil`, and error page fetch.
22
+ *
23
+ * If the request matches a static asset, returns the asset response
24
+ * directly. Otherwise calls `next()` to continue the middleware chain.
25
+ */
26
+ export declare function cf(): HonoMiddlewareHandler;
27
+ export {};
package/dist/hono.js ADDED
@@ -0,0 +1,21 @@
1
+ import { FetchState } from "astro/fetch";
2
+ import { cf as cfFetch } from "./fetch.js";
3
+ const FETCH_STATE_KEY = "fetchState";
4
+ function getFetchState(context) {
5
+ const state = context.get?.(FETCH_STATE_KEY);
6
+ if (state) return state;
7
+ const nextState = new FetchState(context.req.raw);
8
+ context.set?.(FETCH_STATE_KEY, nextState);
9
+ return nextState;
10
+ }
11
+ function cf() {
12
+ return async (context, next) => {
13
+ const state = getFetchState(context);
14
+ const asset = await cfFetch(state, context.env, context.executionCtx);
15
+ if (asset) return asset;
16
+ await next();
17
+ };
18
+ }
19
+ export {
20
+ cf
21
+ };
package/dist/index.js CHANGED
@@ -61,9 +61,9 @@ function createIntegration({
61
61
  ...cloudflareOptions
62
62
  } = {}) {
63
63
  let _config;
64
+ let _buildOutput;
64
65
  let _originalClientDir;
65
66
  let _routes;
66
- let _isFullyStatic = false;
67
67
  let cfPluginConfig;
68
68
  const { buildService, runtimeService } = normalizeImageServiceConfig(imageService);
69
69
  const needsImagesBinding = runtimeService === "cloudflare-binding";
@@ -101,7 +101,7 @@ function createIntegration({
101
101
  const needsImagesBindingForDev = isCompile && command === "dev";
102
102
  const usesContentCollections = hasContentCollectionsConfig(config.srcDir);
103
103
  const prebundleContentRuntime = command === "dev" && usesContentCollections;
104
- cfPluginConfig = {
104
+ const adapterPluginConfig = {
105
105
  config: cloudflareConfigCustomizer({
106
106
  needsSessionKVBinding,
107
107
  sessionKVBindingName,
@@ -127,8 +127,9 @@ function createIntegration({
127
127
  }
128
128
  }
129
129
  };
130
+ cfPluginConfig = { ...cloudflareOptions, ...adapterPluginConfig };
130
131
  if (command === "preview") {
131
- globalThis.astroCloudflareOptions = cfPluginConfig;
132
+ globalThis.astroCloudflareConfig = cfPluginConfig;
132
133
  }
133
134
  const prismFiles = [
134
135
  "@astrojs/prism > prismjs",
@@ -146,9 +147,9 @@ function createIntegration({
146
147
  plugins: [
147
148
  ...prerenderEnvironment === "node" && command === "dev" ? [createNodePrerenderPlugin()] : [],
148
149
  cfVitePlugin({
149
- ...cloudflareOptions,
150
150
  ...cfPluginConfig,
151
- viteEnvironment: { name: "ssr" }
151
+ viteEnvironment: { name: "ssr" },
152
+ assetsOnly: () => _buildOutput === "static"
152
153
  }),
153
154
  {
154
155
  name: "@astrojs/cloudflare:cf-imports",
@@ -269,11 +270,10 @@ function createIntegration({
269
270
  },
270
271
  "astro:routes:resolved": ({ routes }) => {
271
272
  _routes = routes;
272
- const nonInternalRoutes = routes.filter((route) => route.origin !== "internal");
273
- _isFullyStatic = nonInternalRoutes.length > 0 && nonInternalRoutes.every((route) => route.isPrerendered);
274
273
  },
275
- "astro:config:done": ({ setAdapter, config, injectTypes, logger }) => {
274
+ "astro:config:done": ({ setAdapter, config, injectTypes, logger, buildOutput }) => {
276
275
  _config = config;
276
+ _buildOutput = buildOutput;
277
277
  _originalClientDir = new URL(config.build.client.href);
278
278
  if (config.base !== "/") {
279
279
  config.build.client = new URL("." + config.base + "/", config.build.client);
@@ -285,9 +285,10 @@ function createIntegration({
285
285
  setAdapter({
286
286
  name: "@astrojs/cloudflare",
287
287
  adapterFeatures: {
288
- buildOutput: "server",
288
+ buildOutput,
289
289
  middlewareMode: "classic",
290
- preserveBuildClientDir: true
290
+ preserveBuildClientDir: true,
291
+ preserveBuildServerDir: true
291
292
  },
292
293
  entrypointResolution: "auto",
293
294
  previewEntrypoint: "@astrojs/cloudflare/entrypoints/preview",
@@ -322,7 +323,6 @@ function createIntegration({
322
323
  if (prerenderEnvironment === "workerd") {
323
324
  setPrerenderer(
324
325
  createCloudflarePrerenderer({
325
- cloudflareOptions,
326
326
  root: _config.root,
327
327
  serverDir: _config.build.server,
328
328
  clientDir: _config.build.client,
@@ -410,7 +410,7 @@ function createIntegration({
410
410
  )
411
411
  ),
412
412
  dir,
413
- buildOutput: _isFullyStatic ? "static" : "server",
413
+ buildOutput: _buildOutput,
414
414
  assets
415
415
  });
416
416
  if (!trueRedirects.empty()) {
@@ -1,7 +1,6 @@
1
1
  import type { AstroConfig, AstroPrerenderer } from 'astro';
2
2
  import { type PluginConfig } from '@cloudflare/vite-plugin';
3
3
  interface CloudflarePrerendererOptions {
4
- cloudflareOptions: Partial<PluginConfig>;
5
4
  root: AstroConfig['root'];
6
5
  serverDir: AstroConfig['build']['server'];
7
6
  clientDir: AstroConfig['build']['client'];
@@ -14,5 +13,5 @@ interface CloudflarePrerendererOptions {
14
13
  * Creates a prerenderer that uses Cloudflare's workerd runtime via a preview server.
15
14
  * This allows prerendering to happen in the same runtime that will serve the pages.
16
15
  */
17
- export declare function createCloudflarePrerenderer({ cloudflareOptions, root, serverDir, clientDir, base, trailingSlash, cfPluginConfig, hasCompileImageService, }: CloudflarePrerendererOptions): AstroPrerenderer;
16
+ export declare function createCloudflarePrerenderer({ root, serverDir, clientDir, base, trailingSlash, cfPluginConfig, hasCompileImageService, }: CloudflarePrerendererOptions): AstroPrerenderer;
18
17
  export {};
@@ -9,7 +9,6 @@ import {
9
9
  STATIC_IMAGES_ENDPOINT
10
10
  } from "./utils/prerender-constants.js";
11
11
  function createCloudflarePrerenderer({
12
- cloudflareOptions,
13
12
  root,
14
13
  serverDir,
15
14
  clientDir,
@@ -49,13 +48,7 @@ function createCloudflarePrerenderer({
49
48
  // Let the OS pick a free port
50
49
  open: false
51
50
  },
52
- plugins: [
53
- cfVitePlugin({
54
- ...cloudflareOptions,
55
- ...cfPluginConfig,
56
- viteEnvironment: { name: "prerender" }
57
- })
58
- ]
51
+ plugins: [cfVitePlugin({ ...cfPluginConfig, viteEnvironment: { name: "prerender" } })]
59
52
  });
60
53
  const address = previewServer.httpServer.address();
61
54
  if (address && typeof address === "object") {
@@ -0,0 +1,33 @@
1
+ export interface Runtime {
2
+ cfContext: ExecutionContext;
3
+ }
4
+ /** Minimal manifest shape needed by the Cloudflare helpers. */
5
+ export interface ManifestLike {
6
+ assets: Set<string>;
7
+ sessionConfig?: {
8
+ options?: Record<string, unknown>;
9
+ } | undefined;
10
+ }
11
+ /**
12
+ * Returns a `Response` from the ASSETS binding if the request pathname
13
+ * is a known static asset. Returns `undefined` otherwise.
14
+ */
15
+ export declare function matchStaticAsset(manifest: ManifestLike, requestUrl: string, env: Env): Response | undefined;
16
+ /**
17
+ * Tries the ASSETS binding as a fallback for an unmatched route.
18
+ * Returns the asset `Response` if found (non-404), `undefined` otherwise.
19
+ */
20
+ export declare function fallbackToAssets(requestUrl: string, env: Env): Promise<Response | undefined>;
21
+ /**
22
+ * Creates a fetch function for prerendered error pages via the ASSETS binding.
23
+ */
24
+ export declare function createErrorPageFetch(env: Env): (url: string) => Promise<Response>;
25
+ /**
26
+ * Creates the Cloudflare-specific locals object with `cfContext`
27
+ * and deprecated `runtime` property getters.
28
+ */
29
+ export declare function createLocals(ctx: ExecutionContext): Runtime;
30
+ /**
31
+ * Extracts the client IP address from the `cf-connecting-ip` header.
32
+ */
33
+ export declare function getClientAddress(request: Request): string | undefined;
@@ -0,0 +1,63 @@
1
+ import { getValidatedIpFromHeader } from "@astrojs/internal-helpers/request";
2
+ function matchStaticAsset(manifest, requestUrl, env) {
3
+ const { pathname } = new URL(requestUrl);
4
+ if (manifest.assets.has(pathname)) {
5
+ return env.ASSETS.fetch(requestUrl.replace(/\.html$/, ""));
6
+ }
7
+ return void 0;
8
+ }
9
+ async function fallbackToAssets(requestUrl, env) {
10
+ const asset = await env.ASSETS.fetch(
11
+ requestUrl.replace(/index.html$/, "").replace(/\.html$/, "")
12
+ );
13
+ if (asset.status !== 404) {
14
+ return asset;
15
+ }
16
+ return void 0;
17
+ }
18
+ function createErrorPageFetch(env) {
19
+ return async (url) => {
20
+ return env.ASSETS.fetch(url.replace(/\.html$/, ""));
21
+ };
22
+ }
23
+ function createLocals(ctx) {
24
+ const locals = {
25
+ cfContext: ctx
26
+ };
27
+ Object.defineProperty(locals, "runtime", {
28
+ enumerable: false,
29
+ value: {
30
+ get env() {
31
+ throw new Error(
32
+ `Astro.locals.runtime.env has been removed in Astro v6. Use 'import { env } from "cloudflare:workers"' instead.`
33
+ );
34
+ },
35
+ get cf() {
36
+ throw new Error(
37
+ `Astro.locals.runtime.cf has been removed in Astro v6. Use 'Astro.request.cf' instead.`
38
+ );
39
+ },
40
+ get caches() {
41
+ throw new Error(
42
+ `Astro.locals.runtime.caches has been removed in Astro v6. Use the global 'caches' object instead.`
43
+ );
44
+ },
45
+ get ctx() {
46
+ throw new Error(
47
+ `Astro.locals.runtime.ctx has been removed in Astro v6. Use 'Astro.locals.cfContext' instead.`
48
+ );
49
+ }
50
+ }
51
+ });
52
+ return locals;
53
+ }
54
+ function getClientAddress(request) {
55
+ return getValidatedIpFromHeader(request.headers.get("cf-connecting-ip"));
56
+ }
57
+ export {
58
+ createErrorPageFetch,
59
+ createLocals,
60
+ fallbackToAssets,
61
+ getClientAddress,
62
+ matchStaticAsset
63
+ };
@@ -0,0 +1,8 @@
1
+ import type { ManifestLike } from './cf-helpers.js';
2
+ export type { Runtime, ManifestLike } from './cf-helpers.js';
3
+ export { matchStaticAsset, fallbackToAssets, createErrorPageFetch, createLocals, getClientAddress, } from './cf-helpers.js';
4
+ /**
5
+ * Injects the SESSION KV binding into the app manifest's session config.
6
+ * Idempotent — safe to call on every request.
7
+ */
8
+ export declare function injectSessionBinding(manifest: ManifestLike, env: Env): void;
@@ -0,0 +1,24 @@
1
+ import { sessionKVBindingName } from "virtual:astro-cloudflare:config";
2
+ import {
3
+ matchStaticAsset,
4
+ fallbackToAssets,
5
+ createErrorPageFetch,
6
+ createLocals,
7
+ getClientAddress
8
+ } from "./cf-helpers.js";
9
+ function injectSessionBinding(manifest, env) {
10
+ if (env[sessionKVBindingName]) {
11
+ const sessionConfigOptions = manifest.sessionConfig?.options ?? {};
12
+ Object.assign(sessionConfigOptions, {
13
+ binding: env[sessionKVBindingName]
14
+ });
15
+ }
16
+ }
17
+ export {
18
+ createErrorPageFetch,
19
+ createLocals,
20
+ fallbackToAssets,
21
+ getClientAddress,
22
+ injectSessionBinding,
23
+ matchStaticAsset
24
+ };
@@ -1,9 +1,7 @@
1
- export interface Runtime {
2
- cfContext: ExecutionContext;
3
- }
1
+ import { type Runtime } from './cf.js';
2
+ export type { Runtime };
4
3
  declare global {
5
4
  var __ASTRO_IMAGES_BINDING_NAME: string;
6
5
  }
7
6
  type CfResponse = Awaited<ReturnType<Required<ExportedHandler<Env>>['fetch']>>;
8
7
  export declare function handle(request: Request, env: Env, context: ExecutionContext): Promise<CfResponse>;
9
- export {};
@@ -1,9 +1,5 @@
1
1
  import { env as globalEnv } from "cloudflare:workers";
2
- import {
3
- sessionKVBindingName,
4
- compileImageConfig,
5
- isPrerender
6
- } from "virtual:astro-cloudflare:config";
2
+ import { compileImageConfig, isPrerender } from "virtual:astro-cloudflare:config";
7
3
  import { createApp } from "astro/app/entrypoint";
8
4
  import { setGetEnv } from "astro/env/setup";
9
5
  import { createGetEnv } from "../utils/env.js";
@@ -15,7 +11,14 @@ import {
15
11
  isStaticImagesRequest,
16
12
  handleStaticImagesRequest
17
13
  } from "./prerender.js";
18
- import { getValidatedIpFromHeader } from "@astrojs/internal-helpers/request";
14
+ import {
15
+ injectSessionBinding,
16
+ matchStaticAsset,
17
+ fallbackToAssets,
18
+ createErrorPageFetch,
19
+ createLocals,
20
+ getClientAddress
21
+ } from "./cf.js";
19
22
  setGetEnv(createGetEnv(globalEnv));
20
23
  const app = createApp();
21
24
  async function handle(request, env, context) {
@@ -34,16 +37,9 @@ async function handle(request, env, context) {
34
37
  return handleStaticImagesRequest();
35
38
  }
36
39
  }
37
- const { pathname: requestPathname } = new URL(request.url);
38
- if (env[sessionKVBindingName]) {
39
- const sessionConfigOptions = app.manifest.sessionConfig?.options ?? {};
40
- Object.assign(sessionConfigOptions, {
41
- binding: env[sessionKVBindingName]
42
- });
43
- }
44
- if (app.manifest.assets.has(requestPathname)) {
45
- return env.ASSETS.fetch(request.url.replace(/\.html$/, ""));
46
- }
40
+ injectSessionBinding(app.manifest, env);
41
+ const staticAsset = matchStaticAsset(app.manifest, request.url, env);
42
+ if (staticAsset) return staticAsset;
47
43
  let routeData = void 0;
48
44
  if (app.isDev()) {
49
45
  const result = await app.devMatch(app.getPathnameFromRequest(request));
@@ -54,50 +50,17 @@ async function handle(request, env, context) {
54
50
  routeData = app.match(request);
55
51
  }
56
52
  if (!routeData) {
57
- const asset = await env.ASSETS.fetch(
58
- request.url.replace(/index.html$/, "").replace(/\.html$/, "")
59
- );
60
- if (asset.status !== 404) {
61
- return asset;
62
- }
53
+ const asset = await fallbackToAssets(request.url, env);
54
+ if (asset) return asset;
63
55
  }
64
- const locals = {
65
- cfContext: context
66
- };
67
- Object.defineProperty(locals, "runtime", {
68
- enumerable: false,
69
- value: {
70
- get env() {
71
- throw new Error(
72
- `Astro.locals.runtime.env has been removed in Astro v6. Use 'import { env } from "cloudflare:workers"' instead.`
73
- );
74
- },
75
- get cf() {
76
- throw new Error(
77
- `Astro.locals.runtime.cf has been removed in Astro v6. Use 'Astro.request.cf' instead.`
78
- );
79
- },
80
- get caches() {
81
- throw new Error(
82
- `Astro.locals.runtime.caches has been removed in Astro v6. Use the global 'caches' object instead.`
83
- );
84
- },
85
- get ctx() {
86
- throw new Error(
87
- `Astro.locals.runtime.ctx has been removed in Astro v6. Use 'Astro.locals.cfContext' instead.`
88
- );
89
- }
90
- }
91
- });
56
+ const locals = createLocals(context);
92
57
  const waitUntil = context.waitUntil.bind(context);
93
58
  const response = await app.render(request, {
94
59
  routeData,
95
60
  locals,
96
61
  waitUntil,
97
- prerenderedErrorPageFetch: async (url) => {
98
- return env.ASSETS.fetch(url.replace(/\.html$/, ""));
99
- },
100
- clientAddress: getValidatedIpFromHeader(request.headers.get("cf-connecting-ip"))
62
+ prerenderedErrorPageFetch: createErrorPageFetch(env),
63
+ clientAddress: getClientAddress(request)
101
64
  });
102
65
  if (app.setCookieHeaders) {
103
66
  for (const setCookieHeader of app.setCookieHeaders(response)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrojs/cloudflare",
3
3
  "description": "Deploy your site to Cloudflare Workers",
4
- "version": "13.5.5",
4
+ "version": "13.6.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -27,6 +27,8 @@
27
27
  "./image-passthrough-endpoint": "./dist/entrypoints/image-passthrough-endpoint.js",
28
28
  "./image-service-workerd": "./dist/entrypoints/image-service-workerd.js",
29
29
  "./handler": "./dist/utils/handler.js",
30
+ "./fetch": "./dist/fetch.js",
31
+ "./hono": "./dist/hono.js",
30
32
  "./types.d.ts": "./types.d.ts",
31
33
  "./package.json": "./package.json"
32
34
  },
@@ -35,11 +37,11 @@
35
37
  "types.d.ts"
36
38
  ],
37
39
  "dependencies": {
38
- "@cloudflare/vite-plugin": "^1.32.3",
40
+ "@cloudflare/vite-plugin": "^1.39.0",
39
41
  "piccolore": "^0.1.3",
40
42
  "tinyglobby": "^0.2.15",
41
43
  "vite": "^7.3.2",
42
- "@astrojs/internal-helpers": "0.9.1",
44
+ "@astrojs/internal-helpers": "0.10.0",
43
45
  "@astrojs/underscore-redirects": "1.0.3"
44
46
  },
45
47
  "peerDependencies": {
@@ -53,7 +55,7 @@
53
55
  "cheerio": "1.2.0",
54
56
  "devalue": "^5.6.3",
55
57
  "prismjs": "^1.30.0",
56
- "astro": "6.3.8",
58
+ "astro": "6.4.0",
57
59
  "astro-scripts": "0.0.14"
58
60
  },
59
61
  "publishConfig": {