From 2c9614469674509b3e3bc21a4471a1aeb9b4141f Mon Sep 17 00:00:00 2001 From: Alexander Niebuhr Date: Mon, 11 Sep 2023 20:04:44 +0200 Subject: [PATCH] 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 * 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 --- .changeset/smart-dragons-taste.md | 5 + packages/integrations/cloudflare/README.md | 26 ++- packages/integrations/cloudflare/package.json | 8 +- packages/integrations/cloudflare/runtime.d.ts | 3 - packages/integrations/cloudflare/src/index.ts | 149 +++++++++++++- .../integrations/cloudflare/src/parser.ts | 134 +++++++++++++ .../cloudflare/src/server.advanced.ts | 13 -- .../cloudflare/src/server.directory.ts | 15 +- .../integrations/cloudflare/test/cf.test.js | 38 +++- .../cloudflare/test/fixtures/cf/.dev.vars | 1 + .../test/fixtures/cf/astro.config.mjs | 8 - .../cloudflare/test/fixtures/cf/wrangler.toml | 4 + pnpm-lock.yaml | 183 +++++++++++++++++- 13 files changed, 535 insertions(+), 52 deletions(-) create mode 100644 .changeset/smart-dragons-taste.md delete mode 100644 packages/integrations/cloudflare/runtime.d.ts create mode 100644 packages/integrations/cloudflare/src/parser.ts create mode 100644 packages/integrations/cloudflare/test/fixtures/cf/.dev.vars delete mode 100644 packages/integrations/cloudflare/test/fixtures/cf/astro.config.mjs create mode 100644 packages/integrations/cloudflare/test/fixtures/cf/wrangler.toml diff --git a/.changeset/smart-dragons-taste.md b/.changeset/smart-dragons-taste.md new file mode 100644 index 000000000..3b79d9410 --- /dev/null +++ b/.changeset/smart-dragons-taste.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': minor +--- + +Add support for Cloudflare Runtime (env vars, caches and req object), using `astro dev` diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md index f49824cd9..b627b44d9 100644 --- a/packages/integrations/cloudflare/README.md +++ b/packages/integrations/cloudflare/README.md @@ -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). @@ -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 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 project’s `public/` directory. diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json index e00078a65..79fb83bb7 100644 --- a/packages/integrations/cloudflare/package.json +++ b/packages/integrations/cloudflare/package.json @@ -42,7 +42,13 @@ "@astrojs/underscore-redirects": "workspace:*", "@cloudflare/workers-types": "^4.20230821.0", "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": { "astro": "workspace:^3.0.12" diff --git a/packages/integrations/cloudflare/runtime.d.ts b/packages/integrations/cloudflare/runtime.d.ts deleted file mode 100644 index e2a72940a..000000000 --- a/packages/integrations/cloudflare/runtime.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type { WorkerRuntime, PagesRuntime } from './dist/runtime'; - -export { getRuntime } from './dist/runtime'; diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 718b1efa8..c70c9c5aa 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -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 { 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 * as fs from 'node:fs'; import * as os from 'node:os'; import { sep } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import glob from 'tiny-glob'; +import { getEnvVars } from './parser.js'; export type { AdvancedRuntime } from './server.advanced'; export type { DirectoryRuntime } from './server.directory'; type Options = { - mode: 'directory' | 'advanced'; + mode?: 'directory' | 'advanced'; 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 { @@ -22,6 +35,17 @@ 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 function getAdapter({ isModeDirectory, functionPerRoute, @@ -66,6 +90,73 @@ export function getAdapter({ }; } +async function getCFObject(runtimeMode: string): Promise { + 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 = { argv: [], env: {}, @@ -85,6 +176,7 @@ export default function createIntegration(args?: Options): AstroIntegration { const isModeDirectory = args?.mode === 'directory'; const functionPerRoute = args?.functionPerRoute ?? false; + const runtimeMode = args?.runtime ?? 'off'; return { name: '@astrojs/cloudflare', @@ -105,15 +197,56 @@ export default function createIntegration(args?: Options): AstroIntegration { _buildConfig = config.build; if (config.output === 'static') { - throw new Error(` - [@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. - -`); + 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.' + ); } if (config.base === SERVER_BUILD_FOLDER) { - throw new Error(` - [@astrojs/cloudflare] \`base: "${SERVER_BUILD_FOLDER}"\` is not allowed. Please change your \`base\` config to something else.`); + throw new AstroError( + '[@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) => { + return; + }, + caches: new CacheStorage( + { cache: true, cachePersist: false }, + new NoOpLog(), + new StorageFactory(), + {} + ), + }, + }); + next(); + } catch { + next(); + } + }); } }, 'astro:build:setup': ({ vite, target }) => { diff --git a/packages/integrations/cloudflare/src/parser.ts b/packages/integrations/cloudflare/src/parser.ts new file mode 100644 index 000000000..d7130ff9d --- /dev/null +++ b/packages/integrations/cloudflare/src/parser.ts @@ -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 , preferring to read . if + * 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; +} diff --git a/packages/integrations/cloudflare/src/server.advanced.ts b/packages/integrations/cloudflare/src/server.advanced.ts index 24358a5e0..6e305b1b9 100644 --- a/packages/integrations/cloudflare/src/server.advanced.ts +++ b/packages/integrations/cloudflare/src/server.advanced.ts @@ -44,19 +44,6 @@ export function createExports(manifest: SSRManifest) { 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) => { - context.waitUntil(promise); - }, - }); - const locals: AdvancedRuntime = { runtime: { waitUntil: (promise: Promise) => { diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/server.directory.ts index 64d820d99..48c97392c 100644 --- a/packages/integrations/cloudflare/src/server.directory.ts +++ b/packages/integrations/cloudflare/src/server.directory.ts @@ -21,7 +21,7 @@ export function createExports(manifest: SSRManifest) { const onRequest = async (context: EventContext) => { const request = context.request as CFRequest & Request; - const { next, env } = context; + const { 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' @@ -41,19 +41,6 @@ export function createExports(manifest: SSRManifest) { 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) => { - context.waitUntil(promise); - }, - name: 'cloudflare', - next, - caches, - cf: request.cf, - }); - const locals: DirectoryRuntime = { runtime: { waitUntil: (promise: Promise) => { diff --git a/packages/integrations/cloudflare/test/cf.test.js b/packages/integrations/cloudflare/test/cf.test.js index 64c406d12..53b1bbf2c 100644 --- a/packages/integrations/cloudflare/test/cf.test.js +++ b/packages/integrations/cloudflare/test/cf.test.js @@ -3,7 +3,7 @@ import { expect } from 'chai'; import * as cheerio from 'cheerio'; import cloudflare from '../dist/index.js'; -describe('Cf metadata and caches', () => { +describe('Wrangler Cloudflare Runtime', () => { /** @type {import('./test-utils').Fixture} */ let fixture; /** @type {import('./test-utils').WranglerCLI} */ @@ -39,3 +39,39 @@ describe('Cf metadata and caches', () => { 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'); + }); +}); diff --git a/packages/integrations/cloudflare/test/fixtures/cf/.dev.vars b/packages/integrations/cloudflare/test/fixtures/cf/.dev.vars new file mode 100644 index 000000000..9296c384b --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/cf/.dev.vars @@ -0,0 +1 @@ +DATABASE_URL="postgresql://lorem" diff --git a/packages/integrations/cloudflare/test/fixtures/cf/astro.config.mjs b/packages/integrations/cloudflare/test/fixtures/cf/astro.config.mjs deleted file mode 100644 index f92829843..000000000 --- a/packages/integrations/cloudflare/test/fixtures/cf/astro.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -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/cf/wrangler.toml b/packages/integrations/cloudflare/test/fixtures/cf/wrangler.toml new file mode 100644 index 000000000..ba0fa64c4 --- /dev/null +++ b/packages/integrations/cloudflare/test/fixtures/cf/wrangler.toml @@ -0,0 +1,4 @@ +name = "test" + +[vars] +COOL = "ME" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f70352874..f062f95f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3628,9 +3628,27 @@ importers: '@cloudflare/workers-types': specifier: ^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: specifier: ^0.19.2 version: 0.19.2 + find-up: + specifier: ^6.3.0 + version: 6.3.0 tiny-glob: specifier: ^0.2.9 version: 0.2.9 @@ -8013,6 +8031,10 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@iarna/toml@2.2.5: + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + dev: false + /@jest/schemas@29.6.3: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8179,6 +8201,63 @@ packages: - supports-color 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): resolution: {integrity: sha512-Zq5DEAY+kIfwJ1NPd43D1mpsbISuiD6N/SuTHrt/8jUoifLwXaReaZMAnvkvbIGOgcB1Hy++A9jZix2taNNYxQ==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} @@ -8645,6 +8724,12 @@ packages: dependencies: '@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: resolution: {integrity: sha512-Yq6rIccwcco0TLD5SMUrIM7Fk7Fe/C0jmNRxJJCLtAF6gebDkPuUjK5EHedxecm69Pi/aA+It39Ux4OHmFhjRw==} dev: false @@ -10033,6 +10118,12 @@ packages: engines: {node: '>=6'} dev: false + /builtins@5.0.1: + resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} + dependencies: + semver: 7.5.4 + dev: false + /bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} @@ -10938,6 +11029,16 @@ packages: domelementtype: 2.3.0 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: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} @@ -11479,6 +11580,21 @@ packages: strip-final-newline: 2.0.0 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: resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} @@ -11686,6 +11802,14 @@ packages: locate-path: 6.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: resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} dependencies: @@ -11872,7 +11996,6 @@ packages: /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - dev: true /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} @@ -12393,6 +12516,11 @@ packages: engines: {node: '>=10.17.0'} 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: resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} engines: {node: '>=14.18.0'} @@ -13124,6 +13252,13 @@ packages: dependencies: 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: resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==} dev: false @@ -13207,7 +13342,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.5.3 + tslib: 2.6.2 dev: false /lru-cache@4.1.5: @@ -14426,6 +14561,15 @@ packages: set-blocking: 2.0.0 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: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -14589,6 +14733,13 @@ packages: dependencies: 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: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} @@ -14664,6 +14815,10 @@ packages: resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} dev: true + /parse-package-name@1.0.0: + resolution: {integrity: sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==} + dev: false + /parse5-htmlparser2-tree-adapter@7.0.0: resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} dependencies: @@ -14700,6 +14855,11 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 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: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -16966,7 +17126,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true /tty-table@4.2.1: resolution: {integrity: sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==} @@ -17188,6 +17347,13 @@ packages: has-symbols: 1.0.3 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: resolution: {integrity: sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==} engines: {node: '>=14.0'} @@ -17430,6 +17596,10 @@ packages: requires-port: 1.0.0 dev: true + /urlpattern-polyfill@4.0.3: + resolution: {integrity: sha512-DOE84vZT2fEcl9gqCUTcnAw5ZY5Id55ikUcziSUntuEFL3pRvavg5kwDmTEUJkeCHInTlV/HexFomgYnzO5kdQ==} + dev: false + /urlpattern-polyfill@8.0.2: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} dev: false @@ -17472,6 +17642,13 @@ packages: spdx-expression-parse: 3.0.1 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: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'}