feat(@astrojs/cloudflare): add runtime support to astro dev (#8426)

* add necessary libs

* cleanup stale code

* add base feature-set of runtime to `astro dev`

* fix lockfile

* remove future code

Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com>

* remove future code

Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com>

* remove future code

Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com>

* remove future code

Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com>

* remove future code

Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com>

* address review comments

* fix linting issue

* add docs & tests

* fix test paths

* add changeset

* update README.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* fix docs & make adapter options optional

* fix package resolve mode

* fix pnpm-lock

---------

Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Alexander Niebuhr 2023-09-11 20:04:44 +02:00 committed by GitHub
parent b384cf4f7d
commit 2c96144696
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 535 additions and 52 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---
Add support for Cloudflare Runtime (env vars, caches and req object), using `astro dev`

View file

@ -142,7 +142,7 @@ declare namespace App {
} }
``` ```
## Environment Variables ### Environment Variables
See Cloudflare's documentation for [working with environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables). See Cloudflare's documentation for [working with environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables).
@ -159,6 +159,30 @@ export function GET({ params }) {
} }
``` ```
### `cloudflare.runtime`
`runtime: "off" | "local" | "remote"`
default `"off"`
This optional flag enables the Astro dev server to populate environment variables and the Cloudflare Request Object, avoiding the need for Wrangler.
- `local`: environment variables are available, but the request object is populated from a static placeholder value.
- `remote`: environment variables and the live, fetched request object are available.
- `off`: the Astro dev server will populate neither environment variables nor the request object. Use Wrangler to access Cloudflare bindings and environment variables.
```js
// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'server',
adapter: cloudflare({
runtime: 'off' | 'local' | 'remote',
}),
});
```
## Headers, Redirects and function invocation routes ## Headers, Redirects and function invocation routes
Cloudflare has support for adding custom [headers](https://developers.cloudflare.com/pages/platform/headers/), configuring static [redirects](https://developers.cloudflare.com/pages/platform/redirects/) and defining which routes should [invoke functions](https://developers.cloudflare.com/pages/platform/functions/routing/#function-invocation-routes). Cloudflare looks for `_headers`, `_redirects`, and `_routes.json` files in your build output directory to configure these features. This means they should be placed in your Astro projects `public/` directory. Cloudflare has support for adding custom [headers](https://developers.cloudflare.com/pages/platform/headers/), configuring static [redirects](https://developers.cloudflare.com/pages/platform/redirects/) and defining which routes should [invoke functions](https://developers.cloudflare.com/pages/platform/functions/routing/#function-invocation-routes). Cloudflare looks for `_headers`, `_redirects`, and `_routes.json` files in your build output directory to configure these features. This means they should be placed in your Astro projects `public/` directory.

View file

@ -42,7 +42,13 @@
"@astrojs/underscore-redirects": "workspace:*", "@astrojs/underscore-redirects": "workspace:*",
"@cloudflare/workers-types": "^4.20230821.0", "@cloudflare/workers-types": "^4.20230821.0",
"esbuild": "^0.19.2", "esbuild": "^0.19.2",
"tiny-glob": "^0.2.9" "tiny-glob": "^0.2.9",
"find-up": "^6.3.0",
"@iarna/toml": "^2.2.5",
"dotenv": "^16.3.1",
"@miniflare/cache": "^2.14.1",
"@miniflare/shared": "^2.14.1",
"@miniflare/storage-memory": "^2.14.1"
}, },
"peerDependencies": { "peerDependencies": {
"astro": "workspace:^3.0.12" "astro": "workspace:^3.0.12"

View file

@ -1,3 +0,0 @@
export type { WorkerRuntime, PagesRuntime } from './dist/runtime';
export { getRuntime } from './dist/runtime';

View file

@ -1,18 +1,31 @@
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; import type { IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental';
import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; import type { AstroAdapter, 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 esbuild from 'esbuild';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as os from 'node:os'; import * as os from 'node:os';
import { sep } from 'node:path'; import { sep } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url'; import { fileURLToPath, pathToFileURL } from 'node:url';
import glob from 'tiny-glob'; import glob from 'tiny-glob';
import { getEnvVars } from './parser.js';
export type { AdvancedRuntime } from './server.advanced'; export type { AdvancedRuntime } from './server.advanced';
export type { DirectoryRuntime } from './server.directory'; export type { DirectoryRuntime } from './server.directory';
type Options = { type Options = {
mode: 'directory' | 'advanced'; mode?: 'directory' | 'advanced';
functionPerRoute?: boolean; functionPerRoute?: boolean;
/**
* 'off': current behaviour (wrangler is needed)
* 'local': use a static req.cf object, and env vars defined in wrangler.toml & .dev.vars (astro dev is enough)
* 'remote': use a dynamic real-live req.cf object, and env vars defined in wrangler.toml & .dev.vars (astro dev is enough)
*/
runtime?: 'off' | 'local' | 'remote';
}; };
interface BuildConfig { interface BuildConfig {
@ -22,6 +35,17 @@ interface BuildConfig {
split?: boolean; 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 function getAdapter({ export function getAdapter({
isModeDirectory, isModeDirectory,
functionPerRoute, functionPerRoute,
@ -66,6 +90,73 @@ export function getAdapter({
}; };
} }
async function getCFObject(runtimeMode: string): Promise<IncomingRequestCfProperties | void> {
const CF_ENDPOINT = 'https://workers.cloudflare.com/cf.json';
const CF_FALLBACK: IncomingRequestCfProperties = {
asOrganization: '',
asn: 395747,
colo: 'DFW',
city: 'Austin',
region: 'Texas',
regionCode: 'TX',
metroCode: '635',
postalCode: '78701',
country: 'US',
continent: 'NA',
timezone: 'America/Chicago',
latitude: '30.27130',
longitude: '-97.74260',
clientTcpRtt: 0,
httpProtocol: 'HTTP/1.1',
requestPriority: 'weight=192;exclusive=0',
tlsCipher: 'AEAD-AES128-GCM-SHA256',
tlsVersion: 'TLSv1.3',
tlsClientAuth: {
certPresented: '0',
certVerified: 'NONE',
certRevoked: '0',
certIssuerDN: '',
certSubjectDN: '',
certIssuerDNRFC2253: '',
certSubjectDNRFC2253: '',
certIssuerDNLegacy: '',
certSubjectDNLegacy: '',
certSerial: '',
certIssuerSerial: '',
certSKI: '',
certIssuerSKI: '',
certFingerprintSHA1: '',
certFingerprintSHA256: '',
certNotBefore: '',
certNotAfter: '',
},
edgeRequestKeepAliveStatus: 0,
hostMetadata: undefined,
clientTrustScore: 99,
botManagement: {
corporateProxy: false,
verifiedBot: false,
ja3Hash: '25b4882c2bcb50cd6b469ff28c596742',
staticResource: false,
detectionIds: [],
score: 99,
},
};
if (runtimeMode === 'local') {
return CF_FALLBACK;
} else if (runtimeMode === 'remote') {
try {
const res = await fetch(CF_ENDPOINT);
const cfText = await res.text();
const storedCf = JSON.parse(cfText);
return storedCf;
} catch (e: any) {
return CF_FALLBACK;
}
}
}
const SHIM = `globalThis.process = { const SHIM = `globalThis.process = {
argv: [], argv: [],
env: {}, env: {},
@ -85,6 +176,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
const isModeDirectory = args?.mode === 'directory'; const isModeDirectory = args?.mode === 'directory';
const functionPerRoute = args?.functionPerRoute ?? false; const functionPerRoute = args?.functionPerRoute ?? false;
const runtimeMode = args?.runtime ?? 'off';
return { return {
name: '@astrojs/cloudflare', name: '@astrojs/cloudflare',
@ -105,15 +197,56 @@ export default function createIntegration(args?: Options): AstroIntegration {
_buildConfig = config.build; _buildConfig = config.build;
if (config.output === 'static') { if (config.output === 'static') {
throw new Error(` throw new AstroError(
[@astrojs/cloudflare] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter. Otherwise, this adapter is not necessary to deploy a static site to Cloudflare. '[@astrojs/cloudflare] `output: "server"` or `output: "hybrid"` is required to use this adapter. Otherwise, this adapter is not necessary to deploy a static site to Cloudflare.'
);
`);
} }
if (config.base === SERVER_BUILD_FOLDER) { if (config.base === SERVER_BUILD_FOLDER) {
throw new Error(` throw new AstroError(
[@astrojs/cloudflare] \`base: "${SERVER_BUILD_FOLDER}"\` is not allowed. Please change your \`base\` config to something else.`); '[@astrojs/cloudflare] `base: "${SERVER_BUILD_FOLDER}"` is not allowed. Please change your `base` config to something else.'
);
}
},
'astro:server:setup': ({ server }) => {
if (runtimeMode !== 'off') {
server.middlewares.use(async function middleware(req, res, next) {
try {
const cf = await getCFObject(runtimeMode);
const vars = await getEnvVars();
const clientLocalsSymbol = Symbol.for('astro.locals');
Reflect.set(req, clientLocalsSymbol, {
runtime: {
env: {
// default binding for static assets will be dynamic once we support mocking of bindings
ASSETS: {},
// this is just a VAR for CF to change build behavior, on dev it should be 0
CF_PAGES: '0',
// will be fetched from git dynamically once we support mocking of bindings
CF_PAGES_BRANCH: 'TBA',
// will be fetched from git dynamically once we support mocking of bindings
CF_PAGES_COMMIT_SHA: 'TBA',
CF_PAGES_URL: `http://${req.headers.host}`,
...vars,
},
cf: cf,
waitUntil: (_promise: Promise<any>) => {
return;
},
caches: new CacheStorage(
{ cache: true, cachePersist: false },
new NoOpLog(),
new StorageFactory(),
{}
),
},
});
next();
} catch {
next();
}
});
} }
}, },
'astro:build:setup': ({ vite, target }) => { 'astro:build:setup': ({ vite, target }) => {

View file

@ -0,0 +1,134 @@
/**
* This file is a derivative work of wrangler by Cloudflare
* An upstream request for exposing this API was made here:
* https://github.com/cloudflare/workers-sdk/issues/3897
*
* Until further notice, we will be using this file as a workaround
* TODO: Tackle this file, once their is an decision on the upstream request
*/
import * as fs from 'node:fs';
import { resolve, dirname } from 'node:path';
import { findUpSync } from 'find-up';
import TOML from '@iarna/toml';
import dotenv from 'dotenv';
function findWranglerToml(
referencePath: string = process.cwd(),
preferJson = false
): string | undefined {
if (preferJson) {
return (
findUpSync(`wrangler.json`, { cwd: referencePath }) ??
findUpSync(`wrangler.toml`, { cwd: referencePath })
);
}
return findUpSync(`wrangler.toml`, { cwd: referencePath });
}
type File = {
file?: string;
fileText?: string;
};
type Location = File & {
line: number;
column: number;
length?: number;
lineText?: string;
suggestion?: string;
};
type Message = {
text: string;
location?: Location;
notes?: Message[];
kind?: 'warning' | 'error';
};
class ParseError extends Error implements Message {
readonly text: string;
readonly notes: Message[];
readonly location?: Location;
readonly kind: 'warning' | 'error';
constructor({ text, notes, location, kind }: Message) {
super(text);
this.name = this.constructor.name;
this.text = text;
this.notes = notes ?? [];
this.location = location;
this.kind = kind ?? 'error';
}
}
const TOML_ERROR_NAME = 'TomlError';
const TOML_ERROR_SUFFIX = ' at row ';
type TomlError = Error & {
line: number;
col: number;
};
function parseTOML(input: string, file?: string): TOML.JsonMap | never {
try {
// Normalize CRLF to LF to avoid hitting https://github.com/iarna/iarna-toml/issues/33.
const normalizedInput = input.replace(/\r\n/g, '\n');
return TOML.parse(normalizedInput);
} catch (err) {
const { name, message, line, col } = err as TomlError;
if (name !== TOML_ERROR_NAME) {
throw err;
}
const text = message.substring(0, message.lastIndexOf(TOML_ERROR_SUFFIX));
const lineText = input.split('\n')[line];
const location = {
lineText,
line: line + 1,
column: col - 1,
file,
fileText: input,
};
throw new ParseError({ text, location });
}
}
export interface DotEnv {
path: string;
parsed: dotenv.DotenvParseOutput;
}
function tryLoadDotEnv(path: string): DotEnv | undefined {
try {
const parsed = dotenv.parse(fs.readFileSync(path));
return { path, parsed };
} catch (e) {
// logger.debug(`Failed to load .env file "${path}":`, e);
}
}
/**
* Loads a dotenv file from <path>, preferring to read <path>.<environment> if
* <environment> is defined and that file exists.
*/
export function loadDotEnv(path: string): DotEnv | undefined {
return tryLoadDotEnv(path);
}
function getVarsForDev(config: any, configPath: string | undefined): any {
const configDir = resolve(dirname(configPath ?? '.'));
const devVarsPath = resolve(configDir, '.dev.vars');
const loaded = loadDotEnv(devVarsPath);
if (loaded !== undefined) {
return {
...config.vars,
...loaded.parsed,
};
} else {
return config.vars;
}
}
export async function getEnvVars() {
let rawConfig;
const configPath = findWranglerToml(process.cwd(), false); // false = args.experimentalJsonConfig
if (!configPath) {
throw new Error('Could not find wrangler.toml');
}
// Load the configuration from disk if available
if (configPath?.endsWith('toml')) {
rawConfig = parseTOML(fs.readFileSync(configPath).toString(), configPath);
}
const vars = getVarsForDev(rawConfig, configPath);
return vars;
}

View file

@ -44,19 +44,6 @@ export function createExports(manifest: SSRManifest) {
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'), {
env,
name: 'cloudflare',
caches,
cf: request.cf,
...context,
waitUntil: (promise: Promise<any>) => {
context.waitUntil(promise);
},
});
const locals: AdvancedRuntime = { const locals: AdvancedRuntime = {
runtime: { runtime: {
waitUntil: (promise: Promise<any>) => { waitUntil: (promise: Promise<any>) => {

View file

@ -21,7 +21,7 @@ export function createExports(manifest: SSRManifest) {
const onRequest = async (context: EventContext<unknown, string, unknown>) => { const onRequest = async (context: EventContext<unknown, string, unknown>) => {
const request = context.request as CFRequest & Request; const request = context.request as CFRequest & Request;
const { next, env } = context; const { env } = context;
// TODO: remove this any cast in the future // 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' // REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
@ -41,19 +41,6 @@ export function createExports(manifest: SSRManifest) {
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'), {
...context,
waitUntil: (promise: Promise<any>) => {
context.waitUntil(promise);
},
name: 'cloudflare',
next,
caches,
cf: request.cf,
});
const locals: DirectoryRuntime = { const locals: DirectoryRuntime = {
runtime: { runtime: {
waitUntil: (promise: Promise<any>) => { waitUntil: (promise: Promise<any>) => {

View file

@ -3,7 +3,7 @@ import { expect } from 'chai';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import cloudflare from '../dist/index.js'; import cloudflare from '../dist/index.js';
describe('Cf metadata and caches', () => { describe('Wrangler Cloudflare Runtime', () => {
/** @type {import('./test-utils').Fixture} */ /** @type {import('./test-utils').Fixture} */
let fixture; let fixture;
/** @type {import('./test-utils').WranglerCLI} */ /** @type {import('./test-utils').WranglerCLI} */
@ -39,3 +39,39 @@ describe('Cf metadata and caches', () => {
expect($('#hasCache').text()).to.equal('true'); expect($('#hasCache').text()).to.equal('true');
}); });
}); });
describe('Astro Cloudflare Runtime', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let devServer;
before(async () => {
fixture = await loadFixture({
root: './fixtures/cf/',
output: 'server',
adapter: cloudflare({
runtime: 'local',
}),
image: {
service: {
entrypoint: 'astro/assets/services/noop',
},
},
});
process.chdir('./test/fixtures/cf');
devServer = await fixture.startDevServer();
});
after(async () => {
await devServer.stop();
});
it('Populates CF, Vars & Bindings', 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');
});
});

View file

@ -0,0 +1 @@
DATABASE_URL="postgresql://lorem"

View file

@ -1,8 +0,0 @@
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare(),
output: 'server',
});

View file

@ -0,0 +1,4 @@
name = "test"
[vars]
COOL = "ME"

View file

@ -3628,9 +3628,27 @@ importers:
'@cloudflare/workers-types': '@cloudflare/workers-types':
specifier: ^4.20230821.0 specifier: ^4.20230821.0
version: 4.20230821.0 version: 4.20230821.0
'@iarna/toml':
specifier: ^2.2.5
version: 2.2.5
'@miniflare/cache':
specifier: ^2.14.1
version: 2.14.1
'@miniflare/shared':
specifier: ^2.14.1
version: 2.14.1
'@miniflare/storage-memory':
specifier: ^2.14.1
version: 2.14.1
dotenv:
specifier: ^16.3.1
version: 16.3.1
esbuild: esbuild:
specifier: ^0.19.2 specifier: ^0.19.2
version: 0.19.2 version: 0.19.2
find-up:
specifier: ^6.3.0
version: 6.3.0
tiny-glob: tiny-glob:
specifier: ^0.2.9 specifier: ^0.2.9
version: 0.2.9 version: 0.2.9
@ -8013,6 +8031,10 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true dev: true
/@iarna/toml@2.2.5:
resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
dev: false
/@jest/schemas@29.6.3: /@jest/schemas@29.6.3:
resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -8179,6 +8201,63 @@ packages:
- supports-color - supports-color
dev: false dev: false
/@miniflare/cache@2.14.1:
resolution: {integrity: sha512-f/o6UBV6UX+MlhjcEch73/wjQvvNo37dgYmP6Pn2ax1/mEHhJ7allNAqenmonT4djNeyB3eEYV3zUl54wCEwrg==}
engines: {node: '>=16.13'}
dependencies:
'@miniflare/core': 2.14.1
'@miniflare/shared': 2.14.1
http-cache-semantics: 4.1.1
undici: 5.20.0
dev: false
/@miniflare/core@2.14.1:
resolution: {integrity: sha512-d+SGAda/VoXq+SKz04oq8ATUwQw5755L87fgPR8pTdR2YbWkxdbmEm1z2olOpDiUjcR86aN6NtCjY6tUC7fqaw==}
engines: {node: '>=16.13'}
dependencies:
'@iarna/toml': 2.2.5
'@miniflare/queues': 2.14.1
'@miniflare/shared': 2.14.1
'@miniflare/watcher': 2.14.1
busboy: 1.6.0
dotenv: 10.0.0
kleur: 4.1.5
set-cookie-parser: 2.6.0
undici: 5.20.0
urlpattern-polyfill: 4.0.3
dev: false
/@miniflare/queues@2.14.1:
resolution: {integrity: sha512-uBzrbBkIgtNoztDpmMMISg/brYtxLHRE7oTaN8OVnq3bG+3nF9kQC42HUz+Vg+sf65UlvhSaqkjllgx+fNtOxQ==}
engines: {node: '>=16.7'}
dependencies:
'@miniflare/shared': 2.14.1
dev: false
/@miniflare/shared@2.14.1:
resolution: {integrity: sha512-73GnLtWn5iP936ctE6ZJrMqGu134KOoIIveq5Yd/B+NnbFfzpuzjCpkLrnqjkDdsxDbruXSb5eTR/SmAdpJxZQ==}
engines: {node: '>=16.13'}
dependencies:
'@types/better-sqlite3': 7.6.4
kleur: 4.1.5
npx-import: 1.1.4
picomatch: 2.3.1
dev: false
/@miniflare/storage-memory@2.14.1:
resolution: {integrity: sha512-lfQbQwopVWd4W5XzrYdp0rhk3dJpvSmv1Wwn9RhNO20WrcuoxpdSzbmpBahsgYVg+OheVaEbS6RpFqdmwwLTog==}
engines: {node: '>=16.13'}
dependencies:
'@miniflare/shared': 2.14.1
dev: false
/@miniflare/watcher@2.14.1:
resolution: {integrity: sha512-dkFvetm5wk6pwunlYb/UkI0yFNb3otLpRm5RDywMUzqObEf+rCiNNAbJe3HUspr2ncZVAaRWcEaDh82vYK5cmw==}
engines: {node: '>=16.13'}
dependencies:
'@miniflare/shared': 2.14.1
dev: false
/@nanostores/preact@0.5.0(nanostores@0.9.3)(preact@10.17.1): /@nanostores/preact@0.5.0(nanostores@0.9.3)(preact@10.17.1):
resolution: {integrity: sha512-Zq5DEAY+kIfwJ1NPd43D1mpsbISuiD6N/SuTHrt/8jUoifLwXaReaZMAnvkvbIGOgcB1Hy++A9jZix2taNNYxQ==} resolution: {integrity: sha512-Zq5DEAY+kIfwJ1NPd43D1mpsbISuiD6N/SuTHrt/8jUoifLwXaReaZMAnvkvbIGOgcB1Hy++A9jZix2taNNYxQ==}
engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
@ -8645,6 +8724,12 @@ packages:
dependencies: dependencies:
'@babel/types': 7.22.10 '@babel/types': 7.22.10
/@types/better-sqlite3@7.6.4:
resolution: {integrity: sha512-dzrRZCYPXIXfSR1/surNbJ/grU3scTaygS0OMzjlGf71i9sc2fGyHPXXiXmEvNIoE0cGwsanEFMVJxPXmco9Eg==}
dependencies:
'@types/node': 18.17.8
dev: false
/@types/canvas-confetti@1.6.0: /@types/canvas-confetti@1.6.0:
resolution: {integrity: sha512-Yq6rIccwcco0TLD5SMUrIM7Fk7Fe/C0jmNRxJJCLtAF6gebDkPuUjK5EHedxecm69Pi/aA+It39Ux4OHmFhjRw==} resolution: {integrity: sha512-Yq6rIccwcco0TLD5SMUrIM7Fk7Fe/C0jmNRxJJCLtAF6gebDkPuUjK5EHedxecm69Pi/aA+It39Ux4OHmFhjRw==}
dev: false dev: false
@ -10033,6 +10118,12 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: false
/builtins@5.0.1:
resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
dependencies:
semver: 7.5.4
dev: false
/bundle-name@3.0.0: /bundle-name@3.0.0:
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -10938,6 +11029,16 @@ packages:
domelementtype: 2.3.0 domelementtype: 2.3.0
domhandler: 5.0.3 domhandler: 5.0.3
/dotenv@10.0.0:
resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==}
engines: {node: '>=10'}
dev: false
/dotenv@16.3.1:
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
engines: {node: '>=12'}
dev: false
/dotenv@8.6.0: /dotenv@8.6.0:
resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -11479,6 +11580,21 @@ packages:
strip-final-newline: 2.0.0 strip-final-newline: 2.0.0
dev: true dev: true
/execa@6.1.0:
resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
cross-spawn: 7.0.3
get-stream: 6.0.1
human-signals: 3.0.1
is-stream: 3.0.0
merge-stream: 2.0.0
npm-run-path: 5.1.0
onetime: 6.0.0
signal-exit: 3.0.7
strip-final-newline: 3.0.0
dev: false
/execa@7.2.0: /execa@7.2.0:
resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
@ -11686,6 +11802,14 @@ packages:
locate-path: 6.0.0 locate-path: 6.0.0
path-exists: 4.0.0 path-exists: 4.0.0
/find-up@6.3.0:
resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
locate-path: 7.2.0
path-exists: 5.0.0
dev: false
/find-yarn-workspace-root2@1.2.16: /find-yarn-workspace-root2@1.2.16:
resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==}
dependencies: dependencies:
@ -11872,7 +11996,6 @@ packages:
/get-stream@6.0.1: /get-stream@6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/get-stream@8.0.1: /get-stream@8.0.1:
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
@ -12393,6 +12516,11 @@ packages:
engines: {node: '>=10.17.0'} engines: {node: '>=10.17.0'}
dev: true dev: true
/human-signals@3.0.1:
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
engines: {node: '>=12.20.0'}
dev: false
/human-signals@4.3.1: /human-signals@4.3.1:
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
engines: {node: '>=14.18.0'} engines: {node: '>=14.18.0'}
@ -13124,6 +13252,13 @@ packages:
dependencies: dependencies:
p-locate: 5.0.0 p-locate: 5.0.0
/locate-path@7.2.0:
resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
p-locate: 6.0.0
dev: false
/lodash.chunk@4.2.0: /lodash.chunk@4.2.0:
resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==} resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==}
dev: false dev: false
@ -13207,7 +13342,7 @@ packages:
/lower-case@2.0.2: /lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies: dependencies:
tslib: 2.5.3 tslib: 2.6.2
dev: false dev: false
/lru-cache@4.1.5: /lru-cache@4.1.5:
@ -14426,6 +14561,15 @@ packages:
set-blocking: 2.0.0 set-blocking: 2.0.0
dev: false dev: false
/npx-import@1.1.4:
resolution: {integrity: sha512-3ShymTWOgqGyNlh5lMJAejLuIv3W1K3fbI5Ewc6YErZU3Sp0PqsNs8UIU1O8z5+KVl/Du5ag56Gza9vdorGEoA==}
dependencies:
execa: 6.1.0
parse-package-name: 1.0.0
semver: 7.5.4
validate-npm-package-name: 4.0.0
dev: false
/nth-check@2.1.1: /nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
dependencies: dependencies:
@ -14589,6 +14733,13 @@ packages:
dependencies: dependencies:
p-limit: 3.1.0 p-limit: 3.1.0
/p-locate@6.0.0:
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
p-limit: 4.0.0
dev: false
/p-map@2.1.0: /p-map@2.1.0:
resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -14664,6 +14815,10 @@ packages:
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
dev: true dev: true
/parse-package-name@1.0.0:
resolution: {integrity: sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==}
dev: false
/parse5-htmlparser2-tree-adapter@7.0.0: /parse5-htmlparser2-tree-adapter@7.0.0:
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
dependencies: dependencies:
@ -14700,6 +14855,11 @@ packages:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'} engines: {node: '>=8'}
/path-exists@5.0.0:
resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/path-is-absolute@1.0.1: /path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -16966,7 +17126,6 @@ packages:
/tslib@2.6.2: /tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: true
/tty-table@4.2.1: /tty-table@4.2.1:
resolution: {integrity: sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==} resolution: {integrity: sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==}
@ -17188,6 +17347,13 @@ packages:
has-symbols: 1.0.3 has-symbols: 1.0.3
which-boxed-primitive: 1.0.2 which-boxed-primitive: 1.0.2
/undici@5.20.0:
resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==}
engines: {node: '>=12.18'}
dependencies:
busboy: 1.6.0
dev: false
/undici@5.23.0: /undici@5.23.0:
resolution: {integrity: sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==} resolution: {integrity: sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==}
engines: {node: '>=14.0'} engines: {node: '>=14.0'}
@ -17430,6 +17596,10 @@ packages:
requires-port: 1.0.0 requires-port: 1.0.0
dev: true dev: true
/urlpattern-polyfill@4.0.3:
resolution: {integrity: sha512-DOE84vZT2fEcl9gqCUTcnAw5ZY5Id55ikUcziSUntuEFL3pRvavg5kwDmTEUJkeCHInTlV/HexFomgYnzO5kdQ==}
dev: false
/urlpattern-polyfill@8.0.2: /urlpattern-polyfill@8.0.2:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
dev: false dev: false
@ -17472,6 +17642,13 @@ packages:
spdx-expression-parse: 3.0.1 spdx-expression-parse: 3.0.1
dev: true dev: true
/validate-npm-package-name@4.0.0:
resolution: {integrity: sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
dependencies:
builtins: 5.0.1
dev: false
/vary@1.1.2: /vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}