feat(@astrojs/cloudfalre): add cloudflare envs to Astro.locals
(#7541)
* add support for advanced mode * add support for directory mode * use asset fallback as in cloudflare's docs * update locals * come up with new runtime in `Astro.locals` * add overwrite protection * minor cleanup * changeset * address review comments * move overwrite protection to adapter * fix types * fix comment * resolve review comments * update changeset * add test * redo ts * fix integration test port * updated tests, add new port * add TODO comment * update changeset * add JSDoc * Update packages/integrations/cloudflare/src/runtime.ts --------- Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
parent
22c944712c
commit
ffcfcddb75
12 changed files with 176 additions and 33 deletions
12
.changeset/fuzzy-jobs-explain.md
Normal file
12
.changeset/fuzzy-jobs-explain.md
Normal file
|
@ -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;
|
||||||
|
```
|
|
@ -73,12 +73,10 @@ It's then possible to update the preview script in your `package.json` to `"prev
|
||||||
|
|
||||||
## Access to the Cloudflare runtime
|
## 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
|
```js
|
||||||
import { getRuntime } from '@astrojs/cloudflare/runtime';
|
const env = Astro.locals.runtime.env;
|
||||||
|
|
||||||
getRuntime(Astro.request);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Depending on your adapter mode (advanced = worker, directory = pages), the runtime object will look a little different due to differences in the Cloudflare API.
|
Depending on your adapter mode (advanced = worker, directory = pages), the runtime object will look a little different due to differences in the Cloudflare API.
|
||||||
|
|
|
@ -21,15 +21,15 @@ interface BuildConfig {
|
||||||
export function getAdapter(isModeDirectory: boolean): AstroAdapter {
|
export function getAdapter(isModeDirectory: boolean): AstroAdapter {
|
||||||
return isModeDirectory
|
return isModeDirectory
|
||||||
? {
|
? {
|
||||||
name: '@astrojs/cloudflare',
|
name: '@astrojs/cloudflare',
|
||||||
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
|
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
|
||||||
exports: ['onRequest', 'manifest'],
|
exports: ['onRequest', 'manifest'],
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
name: '@astrojs/cloudflare',
|
name: '@astrojs/cloudflare',
|
||||||
serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
|
serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
|
||||||
exports: ['default'],
|
exports: ['default'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const SHIM = `globalThis.process = {
|
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);
|
const serverUrl = new URL(_buildConfig.server);
|
||||||
await fs.promises.rm(serverUrl, { recursive: true, force: true });
|
await fs.promises.rm(serverUrl, { recursive: true, force: true });
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// TODO: remove `getRuntime()` in Astro 3.0
|
||||||
import type { Cache, CacheStorage, IncomingRequestCfProperties } from '@cloudflare/workers-types';
|
import type { Cache, CacheStorage, IncomingRequestCfProperties } from '@cloudflare/workers-types';
|
||||||
|
|
||||||
export type WorkerRuntime<T = unknown> = {
|
export type WorkerRuntime<T = unknown> = {
|
||||||
|
@ -21,6 +22,16 @@ export type PagesRuntime<T = unknown, U = unknown> = {
|
||||||
cf?: IncomingRequestCfProperties;
|
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<T = unknown, U = unknown>(
|
export function getRuntime<T = unknown, U = unknown>(
|
||||||
request: Request
|
request: Request
|
||||||
): WorkerRuntime<T> | PagesRuntime<T, U> {
|
): WorkerRuntime<T> | PagesRuntime<T, U> {
|
||||||
|
|
|
@ -12,10 +12,21 @@ type Env = {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface WorkerRuntime {
|
||||||
|
runtime: {
|
||||||
|
waitUntil: (promise: Promise<any>) => void;
|
||||||
|
env: Env;
|
||||||
|
cf: CFRequest['cf'];
|
||||||
|
caches: typeof caches;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createExports(manifest: SSRManifest) {
|
export function createExports(manifest: SSRManifest) {
|
||||||
const app = new App(manifest);
|
const app = new App(manifest);
|
||||||
|
|
||||||
const fetch = async (request: Request & CFRequest, env: Env, context: ExecutionContext) => {
|
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;
|
process.env = env as any;
|
||||||
|
|
||||||
const { pathname } = new URL(request.url);
|
const { pathname } = new URL(request.url);
|
||||||
|
@ -32,6 +43,9 @@ export function createExports(manifest: SSRManifest) {
|
||||||
Symbol.for('astro.clientAddress'),
|
Symbol.for('astro.clientAddress'),
|
||||||
request.headers.get('cf-connecting-ip')
|
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'), {
|
Reflect.set(request, Symbol.for('runtime'), {
|
||||||
env,
|
env,
|
||||||
name: 'cloudflare',
|
name: 'cloudflare',
|
||||||
|
@ -42,7 +56,19 @@ export function createExports(manifest: SSRManifest) {
|
||||||
context.waitUntil(promise);
|
context.waitUntil(promise);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let response = await app.render(request, routeData);
|
|
||||||
|
const locals: WorkerRuntime = {
|
||||||
|
runtime: {
|
||||||
|
waitUntil: (promise: Promise<any>) => {
|
||||||
|
context.waitUntil(promise);
|
||||||
|
},
|
||||||
|
env: env,
|
||||||
|
cf: request.cf,
|
||||||
|
caches: caches,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = await app.render(request, routeData, locals);
|
||||||
|
|
||||||
if (app.setCookieHeaders) {
|
if (app.setCookieHeaders) {
|
||||||
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
||||||
|
|
|
@ -7,28 +7,30 @@ if (!isNode) {
|
||||||
process.env = getProcessEnvProxy();
|
process.env = getProcessEnvProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FunctionRuntime {
|
||||||
|
runtime: {
|
||||||
|
waitUntil: (promise: Promise<any>) => void;
|
||||||
|
env: EventContext<unknown, string, unknown>['env'];
|
||||||
|
cf: CFRequest['cf'];
|
||||||
|
caches: typeof caches;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createExports(manifest: SSRManifest) {
|
export function createExports(manifest: SSRManifest) {
|
||||||
const app = new App(manifest);
|
const app = new App(manifest);
|
||||||
|
|
||||||
const onRequest = async ({
|
const onRequest = async (context: EventContext<unknown, string, unknown>) => {
|
||||||
request,
|
const request = context.request as CFRequest & Request;
|
||||||
next,
|
const { next, env } = context;
|
||||||
...runtimeEnv
|
|
||||||
}: {
|
// TODO: remove this any cast in the future
|
||||||
request: Request & CFRequest;
|
// REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
|
||||||
next: (request: Request) => void;
|
process.env = env as any;
|
||||||
waitUntil: EventContext<unknown, any, unknown>['waitUntil'];
|
|
||||||
} & Record<string, unknown>) => {
|
|
||||||
process.env = runtimeEnv.env as any;
|
|
||||||
|
|
||||||
const { pathname } = new URL(request.url);
|
const { pathname } = new URL(request.url);
|
||||||
// static assets fallback, in case default _routes.json is not used
|
// static assets fallback, in case default _routes.json is not used
|
||||||
if (manifest.assets.has(pathname)) {
|
if (manifest.assets.has(pathname)) {
|
||||||
// we need this so the page does not error
|
return env.ASSETS.fetch(request);
|
||||||
// https://developers.cloudflare.com/pages/platform/functions/advanced-mode/#set-up-a-function
|
|
||||||
return (runtimeEnv.env as EventContext<unknown, string, unknown>['env']).ASSETS.fetch(
|
|
||||||
request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let routeData = app.match(request, { matchNotFound: true });
|
let routeData = app.match(request, { matchNotFound: true });
|
||||||
|
@ -38,17 +40,32 @@ export function createExports(manifest: SSRManifest) {
|
||||||
Symbol.for('astro.clientAddress'),
|
Symbol.for('astro.clientAddress'),
|
||||||
request.headers.get('cf-connecting-ip')
|
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'), {
|
Reflect.set(request, Symbol.for('runtime'), {
|
||||||
...runtimeEnv,
|
...context,
|
||||||
waitUntil: (promise: Promise<any>) => {
|
waitUntil: (promise: Promise<any>) => {
|
||||||
runtimeEnv.waitUntil(promise);
|
context.waitUntil(promise);
|
||||||
},
|
},
|
||||||
name: 'cloudflare',
|
name: 'cloudflare',
|
||||||
next,
|
next,
|
||||||
caches,
|
caches,
|
||||||
cf: request.cf,
|
cf: request.cf,
|
||||||
});
|
});
|
||||||
let response = await app.render(request, routeData);
|
|
||||||
|
const locals: FunctionRuntime = {
|
||||||
|
runtime: {
|
||||||
|
waitUntil: (promise: Promise<any>) => {
|
||||||
|
context.waitUntil(promise);
|
||||||
|
},
|
||||||
|
env: context.env,
|
||||||
|
cf: request.cf,
|
||||||
|
caches: caches,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = await app.render(request, routeData, locals);
|
||||||
|
|
||||||
if (app.setCookieHeaders) {
|
if (app.setCookieHeaders) {
|
||||||
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('Cf metadata and caches', () => {
|
||||||
});
|
});
|
||||||
await fixture.build();
|
await fixture.build();
|
||||||
|
|
||||||
cli = runCLI('./fixtures/cf/', { silent: true, port: 8788 });
|
cli = runCLI('./fixtures/cf/', { silent: false, port: 8788 });
|
||||||
await cli.ready;
|
await cli.ready;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
8
packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs
vendored
Normal file
8
packages/integrations/cloudflare/test/fixtures/runtime/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import cloudflare from '@astrojs/cloudflare';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
adapter: cloudflare(),
|
||||||
|
output: 'server',
|
||||||
|
});
|
9
packages/integrations/cloudflare/test/fixtures/runtime/package.json
vendored
Normal file
9
packages/integrations/cloudflare/test/fixtures/runtime/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "@test/astro-cloudflare-runtime",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/cloudflare": "workspace:*",
|
||||||
|
"astro": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
15
packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro
vendored
Normal file
15
packages/integrations/cloudflare/test/fixtures/runtime/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
const runtime = Astro.locals.runtime;
|
||||||
|
const env = runtime.env;
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Testing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Testing</h1>
|
||||||
|
<div id="cf">{JSON.stringify(runtime.cf)}</div>
|
||||||
|
<div id="env">{JSON.stringify(env)}</div>
|
||||||
|
<div id="hasCache">{!!runtime.caches}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
38
packages/integrations/cloudflare/test/runtime.test.js
Normal file
38
packages/integrations/cloudflare/test/runtime.test.js
Normal file
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
|
@ -3715,6 +3715,15 @@ importers:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../../../../astro
|
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:
|
packages/integrations/cloudflare/test/fixtures/split:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/cloudflare':
|
'@astrojs/cloudflare':
|
||||||
|
|
Loading…
Add table
Reference in a new issue