Skip to content

Commit

Permalink
Merge branch 'main' into fix-css-windows
Browse files Browse the repository at this point in the history
  • Loading branch information
natemoo-re authored Oct 2, 2023
2 parents a32ef31 + 6db2687 commit cde8743
Show file tree
Hide file tree
Showing 20 changed files with 424 additions and 47 deletions.
5 changes: 5 additions & 0 deletions .changeset/funny-cobras-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---

Introduces support for local KV bindings. Enhances development experience by allowing direct integration with `astro dev`.
5 changes: 5 additions & 0 deletions .changeset/hot-readers-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---

Introduces support for local Durable Objects bindings. Enhances development experience by allowing direct integration with `astro dev`.
5 changes: 5 additions & 0 deletions .changeset/hungry-mails-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---

Introduces support for local D1 bindings. Enhances development experience by allowing direct integration with `astro dev`.
5 changes: 5 additions & 0 deletions .changeset/khaki-toes-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---

Introduces support for local R2 bindings. Enhances development experience by allowing direct integration with `astro dev`.
5 changes: 5 additions & 0 deletions .changeset/yellow-kiwis-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---

Introduces support for local Caches bindings. Enhances development experience by allowing direct integration with `astro dev`.
3 changes: 2 additions & 1 deletion packages/integrations/cloudflare/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Astro cloudflare directory mode creates a function directory
functions
functions
.mf
9 changes: 8 additions & 1 deletion packages/integrations/cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,14 @@ export default defineConfig({

## Cloudflare runtime

Gives you access to [environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables).
Gives you access to [environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables), and [Cloudflare bindings](https://developers.cloudflare.com/pages/platform/functions/bindings).

Currently supported bindings:

- [Cloudflare D1](https://developers.cloudflare.com/d1/)
- [Cloudflare R2](https://developers.cloudflare.com/r2/)
- [Cloudflare Workers KV](https://developers.cloudflare.com/kv/)
- [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/)

You can access the runtime from Astro components through `Astro.locals` inside any .astro` file.

Expand Down
1 change: 1 addition & 0 deletions packages/integrations/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"dependencies": {
"@astrojs/underscore-redirects": "workspace:*",
"@cloudflare/workers-types": "^4.20230821.0",
"miniflare": "^3.20230918.0",
"@iarna/toml": "^2.2.5",
"@miniflare/cache": "^2.14.1",
"@miniflare/shared": "^2.14.1",
Expand Down
86 changes: 65 additions & 21 deletions packages/integrations/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import type { AstroConfig, AstroIntegration, RouteData } from 'astro';

import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
import { CacheStorage } from '@miniflare/cache';
import { NoOpLog } from '@miniflare/shared';
import { MemoryStorage } from '@miniflare/storage-memory';
import { AstroError } from 'astro/errors';
import esbuild from 'esbuild';
import { Miniflare } from 'miniflare';
import * as fs from 'node:fs';
import * as os from 'node:os';
import { dirname, relative, sep } from 'node:path';
Expand All @@ -14,7 +12,13 @@ import glob from 'tiny-glob';
import { getAdapter } from './getAdapter.js';
import { deduplicatePatterns } from './utils/deduplicatePatterns.js';
import { getCFObject } from './utils/getCFObject.js';
import { getEnvVars } from './utils/parser.js';
import {
getD1Bindings,
getDOBindings,
getEnvVars,
getKVBindings,
getR2Bindings,
} from './utils/parser.js';
import { prependForwardSlash } from './utils/prependForwardSlash.js';
import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js';
import { wasmModuleLoader } from './utils/wasm-module-loader.js';
Expand Down Expand Up @@ -55,20 +59,10 @@ interface BuildConfig {
split?: boolean;
}

class StorageFactory {
storages = new Map();

storage(namespace: string) {
let storage = this.storages.get(namespace);
if (storage) return storage;
this.storages.set(namespace, (storage = new MemoryStorage()));
return storage;
}
}

export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let _buildConfig: BuildConfig;
let _mf: Miniflare;
let _entryPoints = new Map<RouteData, URL>();

const SERVER_BUILD_FOLDER = '/$server_build/';
Expand Down Expand Up @@ -122,7 +116,55 @@ export default function createIntegration(args?: Options): AstroIntegration {
try {
const cf = await getCFObject(runtimeMode);
const vars = await getEnvVars();
const D1Bindings = await getD1Bindings();
const R2Bindings = await getR2Bindings();
const KVBindings = await getKVBindings();
const DOBindings = await getDOBindings();
let bindingsEnv = new Object({});

// fix for the error "kj/filesystem-disk-unix.c++:1709: warning: PWD environment variable doesn't match current directory."
// note: This mismatch might be primarily due to the test runner.
const originalPWD = process.env.PWD;
process.env.PWD = process.cwd();

_mf = new Miniflare({
modules: true,
script: '',
cache: true,
cachePersist: true,
cacheWarnUsage: true,
d1Databases: D1Bindings,
d1Persist: true,
r2Buckets: R2Bindings,
r2Persist: true,
kvNamespaces: KVBindings,
kvPersist: true,
durableObjects: DOBindings,
durableObjectsPersist: true,
});
await _mf.ready;

for (const D1Binding of D1Bindings) {
const db = await _mf.getD1Database(D1Binding);
Reflect.set(bindingsEnv, D1Binding, db);
}
for (const R2Binding of R2Bindings) {
const bucket = await _mf.getR2Bucket(R2Binding);
Reflect.set(bindingsEnv, R2Binding, bucket);
}
for (const KVBinding of KVBindings) {
const namespace = await _mf.getKVNamespace(KVBinding);
Reflect.set(bindingsEnv, KVBinding, namespace);
}
for (const key in DOBindings) {
if (Object.prototype.hasOwnProperty.call(DOBindings, key)) {
const DO = await _mf.getDurableObjectNamespace(key);
Reflect.set(bindingsEnv, key, DO);
}
}
const mfCache = await _mf.getCaches();

process.env.PWD = originalPWD;
const clientLocalsSymbol = Symbol.for('astro.locals');
Reflect.set(req, clientLocalsSymbol, {
runtime: {
Expand All @@ -136,18 +178,14 @@ export default function createIntegration(args?: Options): AstroIntegration {
// will be fetched from git dynamically once we support mocking of bindings
CF_PAGES_COMMIT_SHA: 'TBA',
CF_PAGES_URL: `http://${req.headers.host}`,
...bindingsEnv,
...vars,
},
cf: cf,
waitUntil: (_promise: Promise<any>) => {
return;
},
caches: new CacheStorage(
{ cache: true, cachePersist: false },
new NoOpLog(),
new StorageFactory(),
{}
),
caches: mfCache,
},
});
next();
Expand All @@ -157,6 +195,12 @@ export default function createIntegration(args?: Options): AstroIntegration {
});
}
},
'astro:server:done': async ({ logger }) => {
if (_mf) {
logger.info('Cleaning up the Miniflare instance, and shutting down the workerd server.');
await _mf.dispose();
}
},
'astro:build:setup': ({ vite, target }) => {
if (target === 'server') {
vite.resolve ||= {};
Expand Down
59 changes: 58 additions & 1 deletion packages/integrations/cloudflare/src/utils/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
* TODO: Tackle this file, once their is an decision on the upstream request
*/

import type {} from '@cloudflare/workers-types/experimental';
import TOML from '@iarna/toml';
import dotenv from 'dotenv';
import { findUpSync } from 'find-up';
import * as fs from 'node:fs';
import { dirname, resolve } from 'node:path';
let _wrangler: any;

function findWranglerToml(
referencePath: string = process.cwd(),
Expand Down Expand Up @@ -119,7 +121,9 @@ function getVarsForDev(config: any, configPath: string | undefined): any {
return config.vars;
}
}
export async function getEnvVars() {

function parseConfig() {
if (_wrangler) return _wrangler;
let rawConfig;
const configPath = findWranglerToml(process.cwd(), false); // false = args.experimentalJsonConfig
if (!configPath) {
Expand All @@ -129,6 +133,59 @@ export async function getEnvVars() {
if (configPath?.endsWith('toml')) {
rawConfig = parseTOML(fs.readFileSync(configPath).toString(), configPath);
}
_wrangler = { rawConfig, configPath };
return { rawConfig, configPath };
}

export async function getEnvVars() {
const { rawConfig, configPath } = parseConfig();
const vars = getVarsForDev(rawConfig, configPath);
return vars;
}

export async function getD1Bindings() {
const { rawConfig } = parseConfig();
if (!rawConfig) return [];
if (!rawConfig?.d1_databases) return [];
const bindings = (rawConfig?.d1_databases as []).map(
(binding: { binding: string }) => binding.binding
);
return bindings;
}

export async function getR2Bindings() {
const { rawConfig } = parseConfig();
if (!rawConfig) return [];
if (!rawConfig?.r2_buckets) return [];
const bindings = (rawConfig?.r2_buckets as []).map(
(binding: { binding: string }) => binding.binding
);
return bindings;
}

export async function getKVBindings() {
const { rawConfig } = parseConfig();
if (!rawConfig) return [];
if (!rawConfig?.kv_namespaces) return [];
const bindings = (rawConfig?.kv_namespaces as []).map(
(binding: { binding: string }) => binding.binding
);
return bindings;
}

export function getDOBindings(): Record<
string,
{ scriptName?: string | undefined; unsafeUniqueKey?: string | undefined; className: string }
> {
const { rawConfig } = parseConfig();
if (!rawConfig) return {};
if (!rawConfig?.durable_objects) return {};
const output = new Object({}) as Record<
string,
{ scriptName?: string | undefined; unsafeUniqueKey?: string | undefined; className: string }
>;
for (const binding of rawConfig?.durable_objects.bindings) {
Reflect.set(output, binding.name, { className: binding.class_name });
}
return output;
}
64 changes: 56 additions & 8 deletions packages/integrations/cloudflare/test/cf.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ describe('Astro Cloudflare Runtime', () => {
adapter: cloudflare({
runtime: 'local',
}),
image: {
service: {
entrypoint: 'astro/assets/services/noop',
},
},
});
process.chdir('./test/fixtures/cf');
devServer = await fixture.startDevServer();
Expand All @@ -68,12 +63,65 @@ describe('Astro Cloudflare Runtime', () => {
await devServer?.stop();
});

it('Populates CF, Vars & Bindings', async () => {
it('adds cf object', async () => {
let res = await fixture.fetch('/');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasRuntime').text()).to.equal('true');
expect($('#hasCache').text()).to.equal('true');
expect($('#hasCF').text()).to.equal('true');
});

it('adds cache mocking', async () => {
let res = await fixture.fetch('/caches');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasCACHE').text()).to.equal('true');
});

it('adds D1 mocking', async () => {
expect(await fixture.pathExists('../.mf/d1')).to.be.true;

let res = await fixture.fetch('/d1');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasDB').text()).to.equal('true');
expect($('#hasPRODDB').text()).to.equal('true');
expect($('#hasACCESS').text()).to.equal('true');
});

it('adds R2 mocking', async () => {
expect(await fixture.pathExists('../.mf/r2')).to.be.true;

let res = await fixture.fetch('/r2');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasBUCKET').text()).to.equal('true');
expect($('#hasPRODBUCKET').text()).to.equal('true');
expect($('#hasACCESS').text()).to.equal('true');
});

it('adds KV mocking', async () => {
expect(await fixture.pathExists('../.mf/kv')).to.be.true;

let res = await fixture.fetch('/kv');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasKV').text()).to.equal('true');
expect($('#hasPRODKV').text()).to.equal('true');
expect($('#hasACCESS').text()).to.equal('true');
});

it('adds DO mocking', async () => {
expect(await fixture.pathExists('../.mf/do')).to.be.true;

let res = await fixture.fetch('/do');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasDO').text()).to.equal('true');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
const runtime = Astro.locals.runtime;
---

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CACHES</title>
</head>
<body>
<pre id="hasCACHE">{!!runtime.caches}</pre>
</body>
</html>
Loading

0 comments on commit cde8743

Please sign in to comment.