diff --git a/.changeset/fuzzy-jobs-explain.md b/.changeset/fuzzy-jobs-explain.md new file mode 100644 index 000000000000..e15f46130f51 --- /dev/null +++ b/.changeset/fuzzy-jobs-explain.md @@ -0,0 +1,12 @@ +--- +'@astrojs/cloudflare': minor +--- + +The `getRuntime` utility has been deprecated and should be updated to the new [`Astro.locals`](https://docs.astro.build/en/guides/middleware/#locals) API. + +```diff +- import { getRuntime } from '@astrojs/cloudflare/runtime'; +- getRuntime(Astro.request); + ++ const runtime = Astro.locals.runtime; +``` diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md index 45f8e01bac9f..61db6effc6ec 100644 --- a/packages/integrations/cloudflare/README.md +++ b/packages/integrations/cloudflare/README.md @@ -73,12 +73,10 @@ It's then possible to update the preview script in your `package.json` to `"prev ## Access to the Cloudflare runtime -You can access all the Cloudflare bindings and environment variables from Astro components and API routes through the adapter API. +You can access all the Cloudflare bindings and environment variables from Astro components and API routes through `Astro.locals`. ```js -import { getRuntime } from '@astrojs/cloudflare/runtime'; - -getRuntime(Astro.request); +const env = Astro.locals.runtime.env; ``` Depending on your adapter mode (advanced = worker, directory = pages), the runtime object will look a little different due to differences in the Cloudflare API. diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 336f747cc463..aa55af3d4413 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -21,15 +21,15 @@ interface BuildConfig { export function getAdapter(isModeDirectory: boolean): AstroAdapter { return isModeDirectory ? { - name: '@astrojs/cloudflare', - serverEntrypoint: '@astrojs/cloudflare/server.directory.js', - exports: ['onRequest', 'manifest'], - } + name: '@astrojs/cloudflare', + serverEntrypoint: '@astrojs/cloudflare/server.directory.js', + exports: ['onRequest', 'manifest'], + } : { - name: '@astrojs/cloudflare', - serverEntrypoint: '@astrojs/cloudflare/server.advanced.js', - exports: ['default'], - }; + name: '@astrojs/cloudflare', + serverEntrypoint: '@astrojs/cloudflare/server.advanced.js', + exports: ['default'], + }; } const SHIM = `globalThis.process = { @@ -210,7 +210,7 @@ export default function createIntegration(args?: Options): AstroIntegration { } } - // // // throw the server folder in the bin + // throw the server folder in the bin const serverUrl = new URL(_buildConfig.server); await fs.promises.rm(serverUrl, { recursive: true, force: true }); diff --git a/packages/integrations/cloudflare/src/runtime.ts b/packages/integrations/cloudflare/src/runtime.ts index cd3dfff4755a..7791f6f1c6c3 100644 --- a/packages/integrations/cloudflare/src/runtime.ts +++ b/packages/integrations/cloudflare/src/runtime.ts @@ -1,3 +1,4 @@ +// TODO: remove `getRuntime()` in Astro 3.0 import type { Cache, CacheStorage, IncomingRequestCfProperties } from '@cloudflare/workers-types'; export type WorkerRuntime = { @@ -21,6 +22,16 @@ export type PagesRuntime = { cf?: IncomingRequestCfProperties; }; +/** + * @deprecated since version 6.8.0 + * The `getRuntime` utility has been deprecated and should be updated to the new [`Astro.locals`](https://docs.astro.build/en/guides/middleware/#locals) API. + * ```diff + * - import { getRuntime } from '@astrojs/cloudflare/runtime'; + * - getRuntime(Astro.request); + * + * + const runtime = Astro.locals.runtime; + * ``` + */ export function getRuntime( request: Request ): WorkerRuntime | PagesRuntime { diff --git a/packages/integrations/cloudflare/src/server.advanced.ts b/packages/integrations/cloudflare/src/server.advanced.ts index 9758b8b19566..1f0fff8fceaf 100644 --- a/packages/integrations/cloudflare/src/server.advanced.ts +++ b/packages/integrations/cloudflare/src/server.advanced.ts @@ -12,10 +12,21 @@ type Env = { name: string; }; +interface WorkerRuntime { + runtime: { + waitUntil: (promise: Promise) => void; + env: Env; + cf: CFRequest['cf']; + caches: typeof caches; + }; +} + export function createExports(manifest: SSRManifest) { const app = new App(manifest); const fetch = async (request: Request & CFRequest, env: Env, context: ExecutionContext) => { + // TODO: remove this any cast in the future + // REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv' process.env = env as any; const { pathname } = new URL(request.url); @@ -32,6 +43,9 @@ export function createExports(manifest: SSRManifest) { Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip') ); + + // `getRuntime()` is deprecated, currently available additionally to new Astro.locals.runtime + // TODO: remove `getRuntime()` in Astro 3.0 Reflect.set(request, Symbol.for('runtime'), { env, name: 'cloudflare', @@ -42,7 +56,19 @@ export function createExports(manifest: SSRManifest) { context.waitUntil(promise); }, }); - let response = await app.render(request, routeData); + + const locals: WorkerRuntime = { + runtime: { + waitUntil: (promise: Promise) => { + context.waitUntil(promise); + }, + env: env, + cf: request.cf, + caches: caches, + }, + }; + + let response = await app.render(request, routeData, locals); if (app.setCookieHeaders) { for (const setCookieHeader of app.setCookieHeaders(response)) { diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/server.directory.ts index 3e9531a56a49..d4e4094de57e 100644 --- a/packages/integrations/cloudflare/src/server.directory.ts +++ b/packages/integrations/cloudflare/src/server.directory.ts @@ -7,28 +7,30 @@ if (!isNode) { process.env = getProcessEnvProxy(); } +interface FunctionRuntime { + runtime: { + waitUntil: (promise: Promise) => void; + env: EventContext['env']; + cf: CFRequest['cf']; + caches: typeof caches; + }; +} + export function createExports(manifest: SSRManifest) { const app = new App(manifest); - const onRequest = async ({ - request, - next, - ...runtimeEnv - }: { - request: Request & CFRequest; - next: (request: Request) => void; - waitUntil: EventContext['waitUntil']; - } & Record) => { - process.env = runtimeEnv.env as any; + const onRequest = async (context: EventContext) => { + const request = context.request as CFRequest & Request; + const { next, env } = context; + + // TODO: remove this any cast in the future + // REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv' + process.env = env as any; const { pathname } = new URL(request.url); // static assets fallback, in case default _routes.json is not used if (manifest.assets.has(pathname)) { - // we need this so the page does not error - // https://developers.cloudflare.com/pages/platform/functions/advanced-mode/#set-up-a-function - return (runtimeEnv.env as EventContext['env']).ASSETS.fetch( - request - ); + return env.ASSETS.fetch(request); } let routeData = app.match(request, { matchNotFound: true }); @@ -38,17 +40,32 @@ export function createExports(manifest: SSRManifest) { Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip') ); + + // `getRuntime()` is deprecated, currently available additionally to new Astro.locals.runtime + // TODO: remove `getRuntime()` in Astro 3.0 Reflect.set(request, Symbol.for('runtime'), { - ...runtimeEnv, + ...context, waitUntil: (promise: Promise) => { - runtimeEnv.waitUntil(promise); + context.waitUntil(promise); }, name: 'cloudflare', next, caches, cf: request.cf, }); - let response = await app.render(request, routeData); + + const locals: FunctionRuntime = { + runtime: { + waitUntil: (promise: Promise) => { + context.waitUntil(promise); + }, + env: context.env, + cf: request.cf, + caches: caches, + }, + }; + + let response = await app.render(request, routeData, locals); if (app.setCookieHeaders) { for (const setCookieHeader of app.setCookieHeaders(response)) { diff --git a/packages/integrations/cloudflare/test/cf.test.js b/packages/integrations/cloudflare/test/cf.test.js index 559df5c7601e..f8ab9c02f1b1 100644 --- a/packages/integrations/cloudflare/test/cf.test.js +++ b/packages/integrations/cloudflare/test/cf.test.js @@ -17,7 +17,7 @@ describe('Cf metadata and caches', () => { }); await fixture.build(); - cli = runCLI('./fixtures/cf/', { silent: true, port: 8788 }); + cli = runCLI('./fixtures/cf/', { silent: false, port: 8788 }); await cli.ready; }); diff --git a/packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs b/packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs new file mode 100644 index 000000000000..f92829843d3c --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs @@ -0,0 +1,8 @@ +import { defineConfig } from 'astro/config'; +import cloudflare from '@astrojs/cloudflare'; + + +export default defineConfig({ + adapter: cloudflare(), + output: 'server', +}); diff --git a/packages/integrations/cloudflare/test/fixtures/runtime/package.json b/packages/integrations/cloudflare/test/fixtures/runtime/package.json new file mode 100644 index 000000000000..71ac16647693 --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/runtime/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/astro-cloudflare-runtime", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/cloudflare": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro new file mode 100644 index 000000000000..320e8e16243f --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro @@ -0,0 +1,15 @@ +--- +const runtime = Astro.locals.runtime; +const env = runtime.env; +--- + + + Testing + + +

Testing

+
{JSON.stringify(runtime.cf)}
+
{JSON.stringify(env)}
+
{!!runtime.caches}
+ + diff --git a/packages/integrations/cloudflare/test/runtime.test.js b/packages/integrations/cloudflare/test/runtime.test.js new file mode 100644 index 000000000000..243c1dd67c1c --- /dev/null +++ b/packages/integrations/cloudflare/test/runtime.test.js @@ -0,0 +1,38 @@ +import { loadFixture, runCLI } from './test-utils.js'; +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import cloudflare from '../dist/index.js'; + +describe('Runtime Locals', () => { + /** @type {import('./test-utils.js').Fixture} */ + let fixture; + /** @type {import('./test-utils.js').WranglerCLI} */ + let cli; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/runtime/', + output: 'server', + adapter: cloudflare(), + }); + await fixture.build(); + + cli = runCLI('./fixtures/runtime/', { silent: true, port: 8793 }); + await cli.ready; + }); + + after(async () => { + await cli.stop(); + }); + + it('has CF and Caches', async () => { + let res = await fetch(`http://localhost:8793/`); + expect(res.status).to.equal(200); + let html = await res.text(); + let $ = cheerio.load(html); + expect($('#cf').text()).to.contain('city'); + expect($('#env').text()).to.contain('SECRET_STUFF'); + expect($('#env').text()).to.contain('secret'); + expect($('#hasCache').text()).to.equal('true'); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec389bfce59e..b57d14b75faf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3715,6 +3715,15 @@ importers: specifier: workspace:* version: link:../../../../../astro + packages/integrations/cloudflare/test/fixtures/runtime: + dependencies: + '@astrojs/cloudflare': + specifier: workspace:* + version: link:../../.. + astro: + specifier: workspace:* + version: link:../../../../../astro + packages/integrations/cloudflare/test/fixtures/split: dependencies: '@astrojs/cloudflare':