diff --git a/adex/runtime/handler.js b/adex/runtime/handler.js index f5c60be..c51b730 100644 --- a/adex/runtime/handler.js +++ b/adex/runtime/handler.js @@ -1,5 +1,4 @@ import { CONSTANTS, emitToHooked } from 'adex/hook' -import { prepareRequest, prepareResponse } from 'adex/http' import { toStatic } from 'adex/ssr' import { renderToStringAsync } from 'adex/utils/isomorphic' import { h } from 'preact' @@ -14,14 +13,17 @@ import { routes as pageRoutes } from '~routes' const html = String.raw -export async function handler(req, res) { - res.statusCode = 200 - - prepareRequest(req) - prepareResponse(res) - - const [url, search] = req.url.split('?') - const baseURL = normalizeRequestUrl(url) +/** + * Core request handler — Fetch API native. + * Receives a standard Request, returns a standard Response. + * Page responses carry an `x-adex-page-route` header so the adapter + * kernel can inject manifest assets before sending to the client. + * @param {Request} request + * @returns {Promise} + */ +export async function handler(request) { + const { pathname } = new URL(request.url) + const baseURL = normalizeRequestUrl(pathname) const { metas, links, title, lang } = toStatic() @@ -29,32 +31,24 @@ export async function handler(req, res) { const matchedInAPI = apiRoutes.find(d => { return d.regex.pattern.test(baseURL) }) + if (matchedInAPI) { const module = await matchedInAPI.module() - const routeParams = getRouteParams(baseURL, matchedInAPI) - req.params = routeParams - const modifiableContext = { - req: req, - res: res, - } - await emitToHooked(CONSTANTS.beforeApiCall, modifiableContext) + const context = { request } + await emitToHooked(CONSTANTS.beforeApiCall, context) const handlerFn = - 'default' in module ? module.default : (_, res) => res.end() - const serverHandler = async (req, res) => { - await handlerFn(req, res) - await emitToHooked(CONSTANTS.afterApiCall, { req, res }) - } - return { - serverHandler, - } - } - return { - serverHandler: async (_, res) => { - res.statusCode = 404 - res.end('Not found') - await emitToHooked(CONSTANTS.afterApiCall, { req, res }) - }, + 'default' in module + ? module.default + : () => new Response('Not found', { status: 404 }) + const response = await handlerFn(context.request) + await emitToHooked(CONSTANTS.afterApiCall, { + request: context.request, + response, + }) + return response } + + return new Response('Not found', { status: 404 }) } const matchedInPages = pageRoutes.find(d => { @@ -65,15 +59,13 @@ export async function handler(req, res) { const routeParams = getRouteParams(baseURL, matchedInPages) // @ts-expect-error - global.location = new URL(req.url, 'http://localhost') + globalThis.location = new URL(request.url) - const modifiableContext = { - req: req, - } - await emitToHooked(CONSTANTS.beforePageRender, modifiableContext) + const context = { request } + await emitToHooked(CONSTANTS.beforePageRender, context) const rendered = await renderToStringAsync( - h(App, { url: modifiableContext.req.url }), + h(App, { url: new URL(context.request.url).pathname }), {} ) @@ -83,30 +75,36 @@ export async function handler(req, res) { title, lang, entryPage: matchedInPages.route, - routeParams: Buffer.from(JSON.stringify(routeParams), 'utf8').toString( - 'base64' - ), + routeParams: btoa(JSON.stringify(routeParams)), body: rendered, }) - modifiableContext.html = htmlString - await emitToHooked(CONSTANTS.afterPageRender, modifiableContext) - htmlString = modifiableContext.html - return { - html: htmlString, - pageRoute: matchedInPages.route, - } + const pageContext = { request: context.request, html: htmlString } + await emitToHooked(CONSTANTS.afterPageRender, pageContext) + htmlString = pageContext.html + + return new Response(htmlString, { + status: 200, + headers: { + 'content-type': 'text/html', + 'x-adex-page-route': matchedInPages.route, + }, + }) } - return { - html: HTMLTemplate({ + return new Response( + HTMLTemplate({ metas, links, title, lang, body: '404 | Not Found', }), - } + { + status: 404, + headers: { 'content-type': 'text/html' }, + } + ) } function HTMLTemplate({ diff --git a/adex/snapshots/tests/minimal-no-ssr.spec.snap.cjs b/adex/snapshots/tests/minimal-no-ssr.spec.snap.cjs index 39011de..a4a4d75 100644 --- a/adex/snapshots/tests/minimal-no-ssr.spec.snap.cjs +++ b/adex/snapshots/tests/minimal-no-ssr.spec.snap.cjs @@ -11,7 +11,10 @@ exports["devMode ssr minimal > gives a static response 1"] = `" - + + + +

Hello World

@@ -31,7 +34,10 @@ exports["devMode ssr minimal > gives a static response 2"] = `" - + + + +

About

diff --git a/adex/snapshots/tests/minimal-tailwind.spec.snap.cjs b/adex/snapshots/tests/minimal-tailwind.spec.snap.cjs index 7db8699..d5c31d2 100644 --- a/adex/snapshots/tests/minimal-tailwind.spec.snap.cjs +++ b/adex/snapshots/tests/minimal-tailwind.spec.snap.cjs @@ -11,7 +11,10 @@ exports["devMode ssr minimal with styles > gives a non-static ssr response 1"] = - + + + +

Hello World

@@ -31,7 +34,10 @@ exports["devMode ssr minimal with styles > gives a static SSR response 1"] = `" - + + + +

About

diff --git a/adex/snapshots/tests/minimal.spec.snap.cjs b/adex/snapshots/tests/minimal.spec.snap.cjs index 9308110..053dcd9 100644 --- a/adex/snapshots/tests/minimal.spec.snap.cjs +++ b/adex/snapshots/tests/minimal.spec.snap.cjs @@ -11,7 +11,10 @@ exports["devMode ssr minimal > gives a non-static ssr response 1"] = `" - + + + +

Hello World

@@ -38,7 +41,10 @@ exports["devMode ssr minimal > gives a static SSR response 1"] = `" - + + + +

About

diff --git a/adex/src/hook.d.ts b/adex/src/hook.d.ts index e8e4f92..8e92290 100644 --- a/adex/src/hook.d.ts +++ b/adex/src/hook.d.ts @@ -1,12 +1,11 @@ -import { IncomingMessage } from 'node:http' - -export type Context = { - req: IncomingMessage - html: string +export type PageRenderContext = { + request: Request + html?: string } export type APIContext = { - req: IncomingMessage + request: Request + response?: Response } export declare const CONSTANTS: { @@ -22,15 +21,15 @@ export declare function hook( ): void export declare function beforePageRender( - fn: (ctx: Omit) => void + fn: (ctx: Omit) => void ): Promise export declare function afterPageRender( - fn: (ctx: Context) => void + fn: (ctx: PageRenderContext) => void ): Promise export declare function beforeAPICall( - fn: (ctx: APIContext) => void + fn: (ctx: Omit) => void ): Promise export declare function afterAPICall( diff --git a/adex/src/http.d.ts b/adex/src/http.d.ts index 71429ff..f2823b2 100644 --- a/adex/src/http.d.ts +++ b/adex/src/http.d.ts @@ -21,3 +21,18 @@ export type ServerResponse = HTTPServerResponse & { export function prepareRequest(req: IncomingMessage): void export function prepareResponse(res: ServerResponse): void + +/** + * Convert a Node.js IncomingMessage to a Fetch API Request. + * Used by adapter kernels to bridge from Node HTTP to Fetch. + */ +export function nodeRequestToFetch(req: HTTPIncomingMessage): Promise + +/** + * Write a Fetch API Response to a Node.js ServerResponse. + * Skips internal x-adex-* headers. Used by adapter kernels. + */ +export function fetchResponseToNode( + response: Response, + res: HTTPServerResponse +): Promise diff --git a/adex/src/http.js b/adex/src/http.js index 31bfe83..7212916 100644 --- a/adex/src/http.js +++ b/adex/src/http.js @@ -28,6 +28,65 @@ export function prepareRequest(req) { } } +/** + * Convert a Node.js IncomingMessage to a Fetch API Request. + * Reconstructs the full URL from req.url + Host header. + * Reads and buffers the body stream. + * @param {import("./http.js").IncomingMessage} req + * @returns {Promise} + */ +export async function nodeRequestToFetch(req) { + const protocol = req.socket?.encrypted ? 'https' : 'http' + const host = req.headers['host'] ?? 'localhost' + const url = new URL(req.url, `${protocol}://${host}`) + + const headers = new Headers() + for (const [key, value] of Object.entries(req.headers)) { + if (Array.isArray(value)) { + for (const v of value) headers.append(key, v) + } else if (value != null) { + headers.set(key, value) + } + } + + const hasBody = req.method !== 'GET' && req.method !== 'HEAD' + let body = undefined + if (hasBody) { + body = await new Promise((resolve, reject) => { + const chunks = [] + req.on('data', chunk => chunks.push(chunk)) + req.on('end', () => resolve(Buffer.concat(chunks))) + req.on('error', reject) + }) + } + + return new Request(url.href, { + method: req.method, + headers, + body: body ?? null, + }) +} + +/** + * Write a Fetch API Response to a Node.js ServerResponse. + * Copies status, headers (skipping x-adex-* internal headers), and streams body. + * @param {Response} response + * @param {import("./http.js").ServerResponse} res + * @returns {Promise} + */ +export async function fetchResponseToNode(response, res) { + res.statusCode = response.status + for (const [key, value] of response.headers.entries()) { + if (key.startsWith('x-adex-')) continue + res.setHeader(key, value) + } + if (response.body) { + const buf = Buffer.from(await response.arrayBuffer()) + res.write(buf) + } + res.end() +} + /** * @param {import("./http.js").ServerResponse} res */ diff --git a/adex/src/vite.d.ts b/adex/src/vite.d.ts index a52cf1f..b94f262 100644 --- a/adex/src/vite.d.ts +++ b/adex/src/vite.d.ts @@ -1,17 +1,45 @@ import { UserConfig, Plugin } from 'vite' import type { Options as FontOptions } from './fonts.js' +import type { RollupOptions } from 'rollup' -export type Adapters = 'node' +export interface AdapterClientInfo { + bundle: boolean + islands: boolean + manifestPath: string + outDir: string +} + +export interface AdapterConfig { + /** npm package name — added to ssr.noExternal so it bundles into the server output */ + name: string + /** + * Returns a Vite plugin that handles dev-mode request serving for this adapter. + * Called by the core adex() plugin with the same islands flag. + */ + devServerPlugin: (options: { islands: boolean }) => Plugin + /** + * Returns the source code string for the virtual:adex:server entry point. + * Core injects this verbatim — all runtime bootstrap logic lives here. + */ + serverEntry: (options: { islands: boolean }) => string + /** + * Optional hook to extend or override the Rollup options used in the SSR + * server build. The base options are passed in; return the final options. + * Use this to add extra `external` patterns (e.g. /^https?:\/\//) or set + * `output.preserveModules: true` for runtimes like Deno. + */ + rollupOptions?: (base: RollupOptions) => RollupOptions +} export interface AdexOptions { fonts?: FontOptions islands?: boolean - adapter?: Adapters + adapter?: AdapterConfig ssr?: boolean __clientConfig?: UserConfig } -export function adex(options: AdexOptions): Plugin[] +export function adex(options?: AdexOptions): Plugin[] declare module 'vite' { interface Plugin { diff --git a/adex/src/vite.js b/adex/src/vite.js index 25cdbe0..7351358 100644 --- a/adex/src/vite.js +++ b/adex/src/vite.js @@ -23,20 +23,80 @@ const cwd = process.cwd() const islandsDir = join(cwd, '.islands') let runningIslandBuild = false -const adapterMap = { - node: 'adex-adapter-node', +/** + * Resolve the adapter, defaulting to adex-adapter-node if none is provided. + * Cached after first resolution so it is only imported once per Vite session. + * Throws a clear, actionable error if the default cannot be found. + * @param {import("./vite.js").AdapterConfig | undefined} adapter + * @returns {Promise} + */ +async function ensureAdapter(adapter) { + if (adapter) return adapter + try { + const mod = await import('adex-adapter-node') + return mod.node() + } catch { + throw new Error( + "[adex] No adapter was provided and 'adex-adapter-node' could not be found.\n" + + 'Install it: npm add adex-adapter-node\n' + + 'Or pass it explicitly: adex({ adapter: node() })' + ) + } +} + +/** + * Creates a proxy Vite plugin that forwards all dev-server hooks to the + * adapter's devServerPlugin. The adapter is resolved lazily on first use so + * that the default (adex-adapter-node) can be imported asynchronously when no + * adapter is explicitly provided. + * + * @param {{ adapter: import("./vite.js").AdapterConfig | undefined, islands: boolean }} options + * @returns {import("vite").Plugin} + */ +function createAdapterDevServerPlugin({ adapter, islands }) { + /** @type {import("vite").Plugin | null} */ + let inner = null + + async function getInner() { + if (inner) return inner + const resolved = await ensureAdapter(adapter) + inner = resolved.devServerPlugin({ islands }) + return inner + } + + return { + name: 'adex-adapter-dev-server', + apply: 'serve', + enforce: 'pre', + async config(...args) { + const p = await getInner() + return p.config?.call(this, ...args) + }, + async configResolved(...args) { + const p = await getInner() + return p.configResolved?.call(this, ...args) + }, + async resolveId(...args) { + const p = await getInner() + return p.resolveId?.call(this, ...args) + }, + configureServer(server) { + // configureServer must return a function (or void) synchronously in + // Vite's plugin contract, but the return value can itself be async. + return async () => { + const plugin = await getInner() + const result = plugin.configureServer?.call(this, server) + if (typeof result === 'function') await result() + } + }, + } } /** * @param {import("./vite.js").AdexOptions} [options] * @returns {(import("vite").Plugin)[]} */ -export function adex({ - fonts, - islands = false, - ssr = true, - adapter: adapter = 'node', -} = {}) { +export function adex({ fonts, islands = false, ssr = true, adapter } = {}) { // @ts-expect-error probably because of the `.filter` return [ preactPages({ @@ -66,73 +126,28 @@ export function adex({ 'virtual:adex:handler', readFileSync(join(__dirname, '../runtime/handler.js'), 'utf8') ), - createVirtualModule( - 'virtual:adex:server', - `import { createServer } from '${adapterMap[adapter]}' - import { dirname, join } from 'node:path' - import { fileURLToPath } from 'node:url' - import { existsSync, readFileSync } from 'node:fs' - import { env } from 'adex/env' - - import 'virtual:adex:font.css' - import 'virtual:adex:global.css' - - const __dirname = dirname(fileURLToPath(import.meta.url)) - - const PORT = parseInt(env.get('PORT', '3000'), 10) - const HOST = env.get('HOST', 'localhost') - - const paths = { - assets: join(__dirname, './assets'), - islands: join(__dirname, './islands'), - client: join(__dirname, '../client'), - } - - function getServerManifest() { - const manifestPath = join(__dirname, 'manifest.json') - if (existsSync(manifestPath)) { - const manifestFile = readFileSync(manifestPath, 'utf8') - return parseManifest(manifestFile) - } - return {} - } - - function getClientManifest() { - const manifestPath = join(__dirname, '../client/manifest.json') - if (existsSync(manifestPath)) { - const manifestFile = readFileSync(manifestPath, 'utf8') - return parseManifest(manifestFile) - } - return {} - } - - function parseManifest(manifestString) { - try { - const manifestJSON = JSON.parse(manifestString) - return manifestJSON - } catch (err) { - return {} - } - } - - const server = createServer({ - port: PORT, - host: HOST, - adex:{ - manifests:{server:getServerManifest(),client:getClientManifest()}, - paths, + // virtual:adex:server — content is adapter-owned, resolved lazily so the + // default adapter (adex-adapter-node) can be imported async if not provided. + { + name: 'adex-virtual-server-entry', + enforce: 'pre', + resolveId(id) { + if (id === 'virtual:adex:server' || id === '/virtual:adex:server') { + return '\0virtual:adex:server' } - }) - - if ('run' in server) { - server.run() - } - - export default server.fetch - ` - ), + }, + async load(id) { + if (id !== '\0virtual:adex:server') return + const resolved = await ensureAdapter(adapter) + return resolved.serverEntry({ islands }) + }, + }, addFontsPlugin(fonts), - adexDevServer({ islands }), + // Dev-server plugin — adapter-owned. If no adapter is given, the default + // (adex-adapter-node) is resolved async on first hook invocation. + // We wrap it in a proxy plugin so all hooks (config, configResolved, + // resolveId, configureServer) are forwarded to the real plugin object. + createAdapterDevServerPlugin({ adapter, islands }), adexBuildPrep({ islands }), adexClientBuilder({ ssr, islands }), islands && adexIslandsBuilder(), @@ -494,93 +509,10 @@ function adexClientSSRBuilder(opts) { } } -/** - * @returns {import("vite").Plugin} - */ -function adexDevServer({ islands = false } = {}) { - const devCSSMap = new Map() - let cfg - return { - name: 'adex-dev-server', - apply: 'serve', - enforce: 'pre', - config() { - return { - ssr: { - noExternal: ['adex/app'], - }, - } - }, - configResolved(_cfg) { - cfg = _cfg - }, - async resolveId(id, importer, meta) { - if (id.endsWith('.css')) { - if (!importer) return - const importerFromRoot = importer.replace(resolve(cfg.root), '') - const resolvedCss = await this.resolve(id, importer, meta) - if (resolvedCss) { - devCSSMap.set( - importerFromRoot, - (devCSSMap.get(importer) ?? []).concat(resolvedCss.id) - ) - } - return - } - }, - configureServer(server) { - return () => { - server.middlewares.use(async function (req, res, next) { - const module = await server.ssrLoadModule('virtual:adex:handler') - if (!module) { - return next() - } - try { - const { html, serverHandler, pageRoute } = await module.handler( - req, - res - ) - if (serverHandler) { - return serverHandler(req, res) - } - const cssLinks = devCSSMap.get(pageRoute) ?? [] - let renderedHTML = html.replace( - '', - ` - - ${cssLinks.map(d => { - return `` - })} - - ` - ) - if (!islands) { - renderedHTML = html.replace( - '', - `` - ) - } - const finalRenderedHTML = await server.transformIndexHtml( - req.url, - renderedHTML - ) - res.setHeader('content-type', 'text/html') - res.write(finalRenderedHTML) - return res.end() - } catch (err) { - server.ssrFixStacktrace(err) - next(err) - } - }) - } - }, - } -} - /** * @param {object} options * @param {import("./fonts.js").Options} options.fonts - * @param {string} options.adapter + * @param {import("./vite.js").AdapterConfig} options.adapter * @param {boolean} options.islands * @returns {import("vite").Plugin} */ @@ -612,11 +544,25 @@ function adexServerBuilder({ fonts, adapter, islands }) { .filter(d => !d.name.startsWith('vite:')) .filter(d => !d.name.startsWith('adex-')) + // Resolve adapter early so we can call rollupOptions() below. + const resolvedAdapter = await ensureAdapter(adapter) + + // Base Rollup options. The adapter may extend these via rollupOptions(). + const baseRollupOptions = { + input: { + index: input, + }, + external: ['adex/ssr', /^jsr:/, /^npm:/], + } + const finalRollupOptions = resolvedAdapter.rollupOptions + ? resolvedAdapter.rollupOptions(baseRollupOptions) + : baseRollupOptions + await build({ configFile: false, ssr: { external: ['preact', 'adex', 'preact-render-to-string'], - noExternal: Object.values(adapterMap), + noExternal: resolvedAdapter?.name ? [resolvedAdapter.name] : [], }, resolve: cfg.resolve, appType: 'custom', @@ -649,71 +595,43 @@ function adexServerBuilder({ fonts, adapter, islands }) { 'virtual:adex:handler', readFileSync(join(__dirname, '../runtime/handler.js'), 'utf8') ), - createVirtualModule( - 'virtual:adex:server', - `import { createServer } from '${adapterMap[adapter]}' - import { dirname, join } from 'node:path' - import { fileURLToPath } from 'node:url' - import { existsSync, readFileSync } from 'node:fs' - import { env } from 'adex/env' - - import 'virtual:adex:font.css' - import 'virtual:adex:global.css' - - const __dirname = dirname(fileURLToPath(import.meta.url)) - - const PORT = parseInt(env.get('PORT', '3000'), 10) - const HOST = env.get('HOST', 'localhost') - - const paths = { - assets: join(__dirname, './assets'), - islands: join(__dirname, './islands'), - client: join(__dirname, '../client'), - } - - function getServerManifest() { - const manifestPath = join(__dirname, 'manifest.json') - if (existsSync(manifestPath)) { - const manifestFile = readFileSync(manifestPath, 'utf8') - return parseManifest(manifestFile) - } - return {} - } - - function getClientManifest() { - const manifestPath = join(__dirname, '../client/manifest.json') - if (existsSync(manifestPath)) { - const manifestFile = readFileSync(manifestPath, 'utf8') - return parseManifest(manifestFile) + // virtual:adex:server — delegate to adapter.serverEntry() so adapters + // own their own bootstrap code. ensureAdapter() handles the default. + { + name: 'adex-virtual-server-entry', + enforce: 'pre', + resolveId(id) { + if ( + id === 'virtual:adex:server' || + id === '/virtual:adex:server' + ) { + return '\0virtual:adex:server' } - return {} - } - - function parseManifest(manifestString) { - try { - const manifestJSON = JSON.parse(manifestString) - return manifestJSON - } catch (err) { - return {} - } - } - - const server = createServer({ - port: PORT, - host: HOST, - adex:{ - manifests:{server:getServerManifest(),client:getClientManifest()}, - paths, - } - }) - - if ('run' in server) { - server.run() - } - - export default server.fetch - ` - ), + }, + async load(id) { + if (id !== '\0virtual:adex:server') return + return resolvedAdapter.serverEntry({ islands }) + }, + }, + // Emit adex.manifest.json into the server output so the adapter + // kernel knows at runtime whether a client bundle was built. + { + name: 'adex-manifest-sidecar', + generateBundle() { + this.emitFile({ + type: 'asset', + fileName: 'adex.manifest.json', + source: JSON.stringify({ + client: { + bundle: !islands, + islands: !!islands, + manifestPath: '../client/manifest.json', + outDir: '../client', + }, + }), + }) + }, + }, addFontsPlugin(fonts), islands && adexIslandsBuilder(), ...sanitizedPlugins, @@ -730,7 +648,7 @@ function adexServerBuilder({ fonts, adapter, islands }) { input: { index: input, }, - external: ['adex/ssr'], + external: ['adex/ssr', /^http:/, /^npm:/, /^jsr:/], }, }, }) diff --git a/adex/tests/fixtures/minimal-no-ssr/package.json b/adex/tests/fixtures/minimal-no-ssr/package.json index 0391cdf..e158d5b 100644 --- a/adex/tests/fixtures/minimal-no-ssr/package.json +++ b/adex/tests/fixtures/minimal-no-ssr/package.json @@ -5,6 +5,7 @@ "version": "0.0.0", "dependencies": { "adex": "workspace:*", + "adex-adapter-node": "workspace:*", "preact": "catalog:", "@preact/preset-vite": "catalog:" }, diff --git a/adex/tests/fixtures/minimal-no-ssr/vite.config.js b/adex/tests/fixtures/minimal-no-ssr/vite.config.js index c23ef59..c93159e 100644 --- a/adex/tests/fixtures/minimal-no-ssr/vite.config.js +++ b/adex/tests/fixtures/minimal-no-ssr/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' import { adex } from 'adex' +import { node } from 'adex-adapter-node' import preact from '@preact/preset-vite' export default defineConfig({ @@ -7,6 +8,7 @@ export default defineConfig({ adex({ islands: false, ssr: false, + adapter: node(), }), preact(), ], diff --git a/adex/tests/fixtures/minimal-tailwind/package.json b/adex/tests/fixtures/minimal-tailwind/package.json index a727196..5520956 100644 --- a/adex/tests/fixtures/minimal-tailwind/package.json +++ b/adex/tests/fixtures/minimal-tailwind/package.json @@ -6,6 +6,7 @@ "dependencies": { "@preact/preset-vite": "catalog:", "adex": "workspace:*", + "adex-adapter-node": "workspace:*", "preact": "catalog:" }, "devDependencies": { diff --git a/adex/tests/fixtures/minimal-tailwind/vite.config.js b/adex/tests/fixtures/minimal-tailwind/vite.config.js index 59f14af..d4d548f 100644 --- a/adex/tests/fixtures/minimal-tailwind/vite.config.js +++ b/adex/tests/fixtures/minimal-tailwind/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' import { adex } from 'adex' +import { node } from 'adex-adapter-node' import preact from '@preact/preset-vite' export default defineConfig({ @@ -7,6 +8,7 @@ export default defineConfig({ adex({ islands: false, ssr: true, + adapter: node(), }), preact(), ], diff --git a/adex/tests/fixtures/minimal/package.json b/adex/tests/fixtures/minimal/package.json index 3645fd8..4edf1e2 100644 --- a/adex/tests/fixtures/minimal/package.json +++ b/adex/tests/fixtures/minimal/package.json @@ -5,6 +5,7 @@ "version": "0.0.0", "dependencies": { "adex": "workspace:*", + "adex-adapter-node": "workspace:*", "preact": "catalog:", "@preact/preset-vite": "catalog:" }, diff --git a/adex/tests/fixtures/minimal/src/api/ping.js b/adex/tests/fixtures/minimal/src/api/ping.js new file mode 100644 index 0000000..142f4d0 --- /dev/null +++ b/adex/tests/fixtures/minimal/src/api/ping.js @@ -0,0 +1,9 @@ +/** + * Simple test API route for the minimal fixture. + * Used by integration tests to verify Fetch-based API handler behavior. + * @param {Request} request + * @returns {Response} + */ +export default function handler(request) { + return Response.json({ ok: true, method: request.method }) +} diff --git a/adex/tests/fixtures/minimal/vite.config.js b/adex/tests/fixtures/minimal/vite.config.js index 59f14af..d4d548f 100644 --- a/adex/tests/fixtures/minimal/vite.config.js +++ b/adex/tests/fixtures/minimal/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' import { adex } from 'adex' +import { node } from 'adex-adapter-node' import preact from '@preact/preset-vite' export default defineConfig({ @@ -7,6 +8,7 @@ export default defineConfig({ adex({ islands: false, ssr: true, + adapter: node(), }), preact(), ], diff --git a/adex/tests/http-bridge.spec.js b/adex/tests/http-bridge.spec.js new file mode 100644 index 0000000..d695acc --- /dev/null +++ b/adex/tests/http-bridge.spec.js @@ -0,0 +1,179 @@ +import { describe, it } from 'node:test' +import assert from 'node:assert' +import { EventEmitter } from 'node:events' +import { nodeRequestToFetch, fetchResponseToNode } from '../src/http.js' + +/** + * Minimal mock of Node's IncomingMessage, enough for nodeRequestToFetch. + */ +function mockIncomingMessage({ + method = 'GET', + url = '/', + headers = { host: 'localhost' }, + body = null, + encrypted = false, +} = {}) { + const req = new EventEmitter() + req.method = method + req.url = url + req.headers = headers + req.socket = { encrypted } + + // Emit body chunks on next tick so callers can attach listeners first + if (body != null && method !== 'GET' && method !== 'HEAD') { + process.nextTick(() => { + req.emit('data', Buffer.from(body)) + req.emit('end') + }) + } else { + process.nextTick(() => req.emit('end')) + } + + return req +} + +/** + * Minimal mock of Node's ServerResponse. + */ +class MockServerResponse { + constructor() { + this.statusCode = 200 + this.headers = {} + this.chunks = [] + this.ended = false + } + setHeader(name, value) { + this.headers[name.toLowerCase()] = value + } + write(data) { + this.chunks.push(data) + } + end() { + this.ended = true + } + body() { + return Buffer.concat(this.chunks.map(c => Buffer.from(c))).toString('utf8') + } +} + +describe('nodeRequestToFetch', () => { + it('converts a GET request with no body', async () => { + const req = mockIncomingMessage({ method: 'GET', url: '/hello' }) + const fetchReq = await nodeRequestToFetch(req) + + assert.strictEqual(fetchReq.method, 'GET') + assert.ok(fetchReq.url.endsWith('/hello')) + assert.strictEqual(fetchReq.body, null) + }) + + it('reconstructs URL from req.url and Host header', async () => { + const req = mockIncomingMessage({ + method: 'GET', + url: '/path?q=1', + headers: { host: 'example.com' }, + }) + const fetchReq = await nodeRequestToFetch(req) + + assert.strictEqual(new URL(fetchReq.url).host, 'example.com') + assert.strictEqual(new URL(fetchReq.url).pathname, '/path') + assert.strictEqual(new URL(fetchReq.url).search, '?q=1') + }) + + it('uses https scheme when socket is encrypted', async () => { + const req = mockIncomingMessage({ + method: 'GET', + url: '/', + headers: { host: 'secure.example.com' }, + encrypted: true, + }) + const fetchReq = await nodeRequestToFetch(req) + + assert.ok(fetchReq.url.startsWith('https://')) + }) + + it('copies request headers', async () => { + const req = mockIncomingMessage({ + method: 'GET', + url: '/', + headers: { + 'host': 'localhost', + 'x-custom-header': 'my-value', + 'accept': 'application/json', + }, + }) + const fetchReq = await nodeRequestToFetch(req) + + assert.strictEqual(fetchReq.headers.get('x-custom-header'), 'my-value') + assert.strictEqual(fetchReq.headers.get('accept'), 'application/json') + }) + + it('buffers POST body into the Request', async () => { + const payload = JSON.stringify({ hello: 'world' }) + const req = mockIncomingMessage({ + method: 'POST', + url: '/api/data', + headers: { 'host': 'localhost', 'content-type': 'application/json' }, + body: payload, + }) + const fetchReq = await nodeRequestToFetch(req) + + assert.strictEqual(fetchReq.method, 'POST') + const text = await fetchReq.text() + assert.strictEqual(text, payload) + }) + + it('HEAD request has no body', async () => { + const req = mockIncomingMessage({ method: 'HEAD', url: '/' }) + const fetchReq = await nodeRequestToFetch(req) + + assert.strictEqual(fetchReq.method, 'HEAD') + assert.strictEqual(fetchReq.body, null) + }) +}) + +describe('fetchResponseToNode', () => { + it('copies status code', async () => { + const response = new Response('', { status: 201 }) + const res = new MockServerResponse() + await fetchResponseToNode(response, res) + + assert.strictEqual(res.statusCode, 201) + assert.strictEqual(res.ended, true) + }) + + it('copies response headers, skipping x-adex-* headers', async () => { + const response = new Response('', { + status: 200, + headers: { + 'content-type': 'application/json', + 'x-adex-page-route': '/should-be-skipped', + 'x-custom': 'kept', + }, + }) + const res = new MockServerResponse() + await fetchResponseToNode(response, res) + + assert.strictEqual(res.headers['content-type'], 'application/json') + assert.strictEqual(res.headers['x-custom'], 'kept') + assert.strictEqual(res.headers['x-adex-page-route'], undefined) + }) + + it('writes body buffer to res', async () => { + const response = new Response('hello adex', { status: 200 }) + const res = new MockServerResponse() + await fetchResponseToNode(response, res) + + assert.strictEqual(res.body(), 'hello adex') + assert.strictEqual(res.ended, true) + }) + + it('handles a 404 response with no body', async () => { + const response = new Response(null, { status: 404 }) + const res = new MockServerResponse() + await fetchResponseToNode(response, res) + + assert.strictEqual(res.statusCode, 404) + assert.strictEqual(res.ended, true) + assert.strictEqual(res.body(), '') + }) +}) diff --git a/adex/tests/minimal.spec.js b/adex/tests/minimal.spec.js index 2e003aa..bdde94d 100644 --- a/adex/tests/minimal.spec.js +++ b/adex/tests/minimal.spec.js @@ -1,4 +1,5 @@ import { after, before, describe, it } from 'node:test' +import assert from 'node:assert' import { devServerURL, launchDemoDevServer } from './utils.js' import { snapshot } from '@barelyhuman/node-snapshot' @@ -36,4 +37,20 @@ describe('devMode ssr minimal', async () => { ) ) }) + + await it('API route returns JSON with 200', async () => { + const response = await fetch(new URL('/api/ping', devServerURL)) + const json = await response.json() + + assert.strictEqual(response.status, 200) + assert.strictEqual(json.ok, true) + assert.strictEqual(json.method, 'GET') + }) + + await it('unknown route returns 404', async () => { + const response = await fetch( + new URL('/this-route-does-not-exist', devServerURL) + ) + assert.strictEqual(response.status, 404) + }) }) diff --git a/packages/adapters/deno/lib/dev.js b/packages/adapters/deno/lib/dev.js new file mode 100644 index 0000000..c934dbf --- /dev/null +++ b/packages/adapters/deno/lib/dev.js @@ -0,0 +1,102 @@ +// Dev-mode Vite plugin for the Deno adapter. +// Vite's dev server always runs on Node.js, so this file uses Node-only APIs +// (adex/http bridge, node:path) and is never imported in the Deno production runtime. + +import { nodeRequestToFetch, fetchResponseToNode } from 'adex/http' +import { resolve } from 'node:path' + +/** + * Creates the Vite dev server plugin for the Deno adapter. + * Even though the production target is Deno, the dev server is always + * Node.js (Vite), so we use the same Node ↔ Fetch bridge as the node adapter. + * + * @param {{ islands: boolean }} options + * @returns {import('vite').Plugin} + */ +export function createDenoDevServerPlugin({ islands = false } = {}) { + const devCSSMap = new Map() + let cfg + + return { + name: 'adex-dev-server', + apply: 'serve', + enforce: 'pre', + config() { + return { + ssr: { + noExternal: ['adex/app'], + }, + } + }, + configResolved(_cfg) { + cfg = _cfg + }, + async resolveId(id, importer, meta) { + if (id.endsWith('.css')) { + if (!importer) return + const importerFromRoot = importer.replace(resolve(cfg.root), '') + const resolvedCss = await this.resolve(id, importer, meta) + if (resolvedCss) { + devCSSMap.set( + importerFromRoot, + (devCSSMap.get(importer) ?? []).concat(resolvedCss.id) + ) + } + return + } + }, + configureServer(server) { + return () => { + server.middlewares.use(async function (req, res, next) { + const module = await server.ssrLoadModule('virtual:adex:handler') + if (!module) { + return next() + } + try { + const fetchRequest = await nodeRequestToFetch(req) + const response = await module.handler(fetchRequest) + const pageRoute = response.headers.get('x-adex-page-route') + + if (!pageRoute) { + // API response or 404 — write directly to node res + await fetchResponseToNode(response, res) + return + } + + // Page response — inject dev CSS preload links + HMR client script + const cssLinks = devCSSMap.get(pageRoute) ?? [] + let html = await response.text() + + html = html.replace( + '', + ` + + ${cssLinks + .map( + d => + `` + ) + .join('')} + ` + ) + + if (!islands) { + html = html.replace( + '', + `` + ) + } + + const finalHTML = await server.transformIndexHtml(req.url, html) + res.setHeader('content-type', 'text/html') + res.write(finalHTML) + res.end() + } catch (err) { + server.ssrFixStacktrace(err) + next(err) + } + }) + } + }, + } +} diff --git a/packages/adapters/deno/lib/index.d.ts b/packages/adapters/deno/lib/index.d.ts new file mode 100644 index 0000000..7c4911d --- /dev/null +++ b/packages/adapters/deno/lib/index.d.ts @@ -0,0 +1,61 @@ +import type { Plugin } from 'vite' +import type { RollupOptions } from 'rollup' + +export interface AdapterClientInfo { + /** true when a full client bundle was emitted to dist/client/ */ + bundle: boolean + /** true when islands were built to dist/server/islands/ */ + islands: boolean +} + +export interface AdexRuntimeConfig { + manifests: { server: object; client: object } + paths: { assets: string; islands: string; client: string } + client: AdapterClientInfo +} + +export interface DenoAdapterOptions { + port?: number | string + hostname?: string +} + +export interface DenoServerOptions { + port?: number | string + hostname?: string + adex?: AdexRuntimeConfig +} + +export interface AdapterConfig { + /** npm package name — added to ssr.noExternal so it bundles into the server output */ + name: string + /** + * Returns a Vite plugin that handles dev-mode request serving. + * Called by the core adex() plugin with the same islands flag. + */ + devServerPlugin: (options: { islands: boolean }) => Plugin + /** + * Returns the source code string for the virtual:adex:server entry point. + * Core injects this verbatim — all runtime bootstrap logic lives here. + */ + serverEntry: (options: { islands: boolean }) => string + /** + * Optional hook to extend/override the Rollup options for the SSR server + * build. The Deno adapter uses this to enable preserveModules and add + * https:// / node: specifiers to external. + */ + rollupOptions?: (base: RollupOptions) => RollupOptions +} + +/** + * Adapter factory — pass to adex({ adapter: deno() }) in vite.config.js + */ +export declare function deno(options?: DenoAdapterOptions): AdapterConfig + +/** + * Runtime server factory — called by the generated virtual:adex:server entry. + * Uses Deno.serve() for the production HTTP server. + */ +export declare const createServer: (options?: DenoServerOptions) => { + run(): void + fetch: (req: Request) => Promise +} diff --git a/packages/adapters/deno/lib/index.js b/packages/adapters/deno/lib/index.js new file mode 100644 index 0000000..fa9a96d --- /dev/null +++ b/packages/adapters/deno/lib/index.js @@ -0,0 +1,320 @@ +// Production runtime for the Deno adapter. +// This file contains only Deno-compatible code — no Node.js APIs, no Buffer. +// The dev-mode Vite plugin (which runs on Node.js) lives in ./dev.js. + +import { createDenoDevServerPlugin } from './dev.js' + +// ─── Adapter factory ────────────────────────────────────────────────────────── + +/** + * Adapter factory — pass to adex({ adapter: deno() }) in vite.config.js + * Returns an AdapterConfig object the core framework uses at build time. + * @param {import('./index.d.ts').DenoAdapterOptions} [options] + * @returns {import('./index.d.ts').AdapterConfig} + */ +export function deno(options = {}) { + return { + name: 'adex-adapter-deno', + + devServerPlugin({ islands }) { + return createDenoDevServerPlugin({ islands }) + }, + + /** + * Extend the Rollup SSR build options for Deno compatibility: + * - preserveModules: true → Rollup emits one file per module and leaves + * import specifiers (jsr:, npm:, https://, node:) untouched, so user + * code can freely use Deno-style imports without Rollup erroring on + * unknown protocols. + * - external additions: https?:// and node: specifiers that may appear + * in user pages / API routes. + * - sanitizeFileName: replaces ':' (invalid on Windows / confusing on + * Unix) in virtual module IDs with '_'. + * The adex core will emit an index.js shim pointing at the real entry + * when the entry is not already named index.js (preserveModules case). + */ + rollupOptions(base) { + const baseExternal = Array.isArray(base.external) + ? base.external + : base.external + ? [base.external] + : [] + return { + ...base, + external: [...baseExternal, /^https?:\/\//, /^node:/], + output: { + ...(Array.isArray(base.output) ? {} : (base.output ?? {})), + preserveModules: true, + sanitizeFileName: name => name.replace(/[:<>|?*]/g, '_'), + }, + } + }, + + serverEntry({ islands }) { + return `import { createServer } from 'adex-adapter-deno' +import { join } from 'jsr:@std/path' +import { env } from 'adex/env' + +import 'virtual:adex:font.css' +import 'virtual:adex:global.css' + +// Deno 1.40+ exposes import.meta.dirname; fall back to URL parsing for older versions. +const __dirname = import.meta.dirname ?? new URL('.', import.meta.url).pathname + +const PORT = parseInt(env.get('PORT', '3000'), 10) +const HOSTNAME = env.get('HOST', 'localhost') + +function readJSON(p) { + try { return JSON.parse(Deno.readTextFileSync(p)) } catch { return {} } +} + +const adexManifest = readJSON(join(__dirname, 'adex.manifest.json')) +const serverManifest = readJSON(join(__dirname, 'manifest.json')) +const clientManifest = adexManifest?.client?.bundle + ? readJSON(join(__dirname, adexManifest.client.manifestPath)) + : {} + +const paths = { + assets: join(__dirname, './assets'), + islands: join(__dirname, './islands'), + client: join(__dirname, adexManifest?.client?.outDir ?? '../client'), +} + +const server = createServer({ + port: PORT, + hostname: HOSTNAME, + adex: { + manifests: { server: serverManifest, client: clientManifest }, + paths, + client: adexManifest?.client ?? { bundle: false, islands: false }, + }, +}) + +if ('run' in server) { server.run() } +export default server.fetch +` + }, + } +} + +// ─── Production server ──────────────────────────────────────────────────────── + +/** + * Build the injected HTML strings for CSS/JS assets from the Vite manifests. + * + * @param {object} manifest + * @param {string} filePath + * @returns {{ links: string[], scripts: string[] }} + */ +function manifestToHTML(manifest, filePath) { + let links = [] + let scripts = [] + + const rootServerFile = 'virtual:adex:server' + if (manifest[rootServerFile]) { + const graph = manifest[rootServerFile] + links = links.concat( + (graph.css || []).map(d => ``) + ) + } + + const rootClientFile = 'virtual:adex:client' + if (manifest[rootClientFile]) { + const graph = manifest[rootClientFile] + links = links.concat( + (graph.css || []).map(d => ``) + ) + } + + if (manifest[filePath]) { + const graph = manifest[filePath] + links = links.concat( + (graph.css || []).map(d => ``) + ) + + const depsHasCSS = (manifest[filePath].imports || []) + .map(d => manifest[d]) + .filter(d => d?.css?.length) + + if (depsHasCSS.length) { + links = links.concat( + depsHasCSS.map(d => + d.css.map(p => ``).join('\n') + ) + ) + } + + scripts = scripts.concat( + `` + ) + } + + return { scripts, links } +} + +/** + * Inject manifest-driven CSS/JS asset tags into an HTML string. + * + * @param {string} template + * @param {string} pageRoute + * @param {object} serverManifest + * @param {object} clientManifest + * @returns {string} + */ +function addDependencyAssets( + template, + pageRoute, + serverManifest, + clientManifest +) { + if (!pageRoute) return template + + const filePath = pageRoute.startsWith('/') ? pageRoute.slice(1) : pageRoute + + const { links: serverLinks } = manifestToHTML(serverManifest, filePath) + const { links: clientLinks, scripts: clientScripts } = manifestToHTML( + clientManifest, + filePath + ) + + const links = [...serverLinks, ...clientLinks] + const scripts = [...clientScripts] + + return template.replace( + '', + links.join('\n') + scripts.join('\n') + '' + ) +} + +/** + * Serve a static file from a directory using Deno's standard library. + * Returns a Response if found, null if not. + * + * @param {Request} request + * @param {string} dir — absolute path to the directory root + * @param {string} prefix — URL prefix to strip before resolving (e.g. '/assets') + * @returns {Promise} + */ +async function serveStatic(request, dir, prefix = '') { + const { serveDir } = await import('jsr:@std/http/file-server') + return serveDir(request, { + fsRoot: dir, + urlRoot: prefix.replace(/^\//, ''), + quiet: true, + }) +} + +/** + * Production Deno server factory. + * + * @param {import('./index.d.ts').DenoServerOptions} options + * @returns {{ run(): void; fetch: (req: Request) => Promise }} + */ +export const createServer = ({ + port = 3000, + hostname = '127.0.0.1', + adex = { + manifests: { server: {}, client: {} }, + paths: {}, + client: { bundle: true, islands: false }, + }, +} = {}) => { + /** @type {((req: Request) => Promise) | null} */ + let fetchHandler = null + + async function getFetchHandler() { + if (fetchHandler) return fetchHandler + + // @ts-expect-error injected by vite + const { handler } = await import('virtual:adex:handler') + + const { manifests, paths, client } = adex + + /** + * @param {Request} request + * @returns {Promise} + */ + async function handle(request) { + const url = new URL(request.url) + const pathname = url.pathname + + // 1. /assets/** — server-emitted static assets (fonts, etc.) + if (pathname.startsWith('/assets/') && paths.assets) { + try { + const resp = await serveStatic(request, paths.assets, '/assets') + if (resp && resp.status !== 404) return resp + } catch { + // fall through to app handler + } + } + + // 2. /islands/** — island JS bundles + if (client.islands && pathname.startsWith('/islands/') && paths.islands) { + try { + const resp = await serveStatic(request, paths.islands, '/islands') + if (resp && resp.status !== 404) return resp + } catch { + // fall through + } + } + + // 3. Client bundle — CSS, hashed JS, etc. + if (client.bundle && paths.client) { + try { + const resp = await serveStatic(request, paths.client, '') + if (resp && resp.status !== 404) return resp + } catch { + // fall through + } + } + + // 4. App handler (SSR pages + API routes) + const response = await handler(request) + const pageRoute = response.headers.get('x-adex-page-route') + + if (!pageRoute) { + // API response or 404 — strip internal headers and return + return new Response(response.body, { + status: response.status, + headers: Object.fromEntries( + [...response.headers.entries()].filter( + ([k]) => !k.startsWith('x-adex-') + ) + ), + }) + } + + // Page response — inject manifest CSS/JS asset tags + const html = await response.text() + const finalHTML = addDependencyAssets( + html, + pageRoute, + manifests.server, + manifests.client + ) + + return new Response(finalHTML, { + status: response.status, + headers: { 'content-type': 'text/html; charset=utf-8' }, + }) + } + + fetchHandler = handle + return fetchHandler + } + + return { + async run() { + const handle = await getFetchHandler() + // @ts-expect-error Deno global — not present in Node type definitions + Deno.serve({ port, hostname }, handle) + console.log(`Listening on http://${hostname}:${port}`) + }, + // Exposed so the server entry can `export default server.fetch` for + // edge/serverless runtimes that call the handler directly. + fetch: async request => { + const handle = await getFetchHandler() + return handle(request) + }, + } +} diff --git a/packages/adapters/deno/package.json b/packages/adapters/deno/package.json new file mode 100644 index 0000000..e4d6ff9 --- /dev/null +++ b/packages/adapters/deno/package.json @@ -0,0 +1,45 @@ +{ + "name": "adex-adapter-deno", + "version": "0.0.21", + "description": "Deno adapter for Adex, enabling server-side rendering via Deno.serve.", + "keywords": [ + "adex", + "preact", + "minimal", + "server", + "deno", + "ssr", + "adapter" + ], + "author": "reaper ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/barelyhuman/adex" + }, + "bugs": { + "url": "https://github.com/barelyhuman/adex/issues" + }, + "homepage": "https://github.com/barelyhuman/adex/tree/main/packages/adapters/deno", + "type": "module", + "main": "./lib/index.js", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.js" + } + }, + "files": [ + "lib" + ], + "engines": { + "deno": ">=1.40.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/barelyhuman" + }, + "dependencies": { + "adex": "workspace:*" + } +} diff --git a/packages/adapters/node/lib/dev.js b/packages/adapters/node/lib/dev.js new file mode 100644 index 0000000..f916ec5 --- /dev/null +++ b/packages/adapters/node/lib/dev.js @@ -0,0 +1,100 @@ +// Dev-mode Vite plugin for the Node.js adapter. +// This file is only ever loaded in a Node.js/Vite context (build tooling), +// so Node-only APIs (Buffer, node:path, adex/http) are safe to use here. + +import { nodeRequestToFetch, fetchResponseToNode } from 'adex/http' +import { resolve } from 'node:path' + +/** + * Creates the Vite dev server plugin for the Node.js adapter. + * + * @param {{ islands: boolean }} options + * @returns {import('vite').Plugin} + */ +export function createNodeDevServerPlugin({ islands = false } = {}) { + const devCSSMap = new Map() + let cfg + + return { + name: 'adex-dev-server', + apply: 'serve', + enforce: 'pre', + config() { + return { + ssr: { + noExternal: ['adex/app'], + }, + } + }, + configResolved(_cfg) { + cfg = _cfg + }, + async resolveId(id, importer, meta) { + if (id.endsWith('.css')) { + if (!importer) return + const importerFromRoot = importer.replace(resolve(cfg.root), '') + const resolvedCss = await this.resolve(id, importer, meta) + if (resolvedCss) { + devCSSMap.set( + importerFromRoot, + (devCSSMap.get(importer) ?? []).concat(resolvedCss.id) + ) + } + return + } + }, + configureServer(server) { + return () => { + server.middlewares.use(async function (req, res, next) { + const module = await server.ssrLoadModule('virtual:adex:handler') + if (!module) { + return next() + } + try { + const fetchRequest = await nodeRequestToFetch(req) + const response = await module.handler(fetchRequest) + const pageRoute = response.headers.get('x-adex-page-route') + + if (!pageRoute) { + // API response or 404 — write directly to node res + await fetchResponseToNode(response, res) + return + } + + // Page response — inject dev CSS preload links + HMR client script + const cssLinks = devCSSMap.get(pageRoute) ?? [] + let html = await response.text() + + html = html.replace( + '', + ` + + ${cssLinks + .map( + d => + `` + ) + .join('')} + ` + ) + + if (!islands) { + html = html.replace( + '', + `` + ) + } + + const finalHTML = await server.transformIndexHtml(req.url, html) + res.setHeader('content-type', 'text/html') + res.write(finalHTML) + res.end() + } catch (err) { + server.ssrFixStacktrace(err) + next(err) + } + }) + } + }, + } +} diff --git a/packages/adapters/node/lib/index.d.ts b/packages/adapters/node/lib/index.d.ts index dd955a7..a20dd9a 100644 --- a/packages/adapters/node/lib/index.d.ts +++ b/packages/adapters/node/lib/index.d.ts @@ -1,6 +1,48 @@ -type ServerOut = { - run: () => any - fetch: undefined +import type { Plugin } from 'vite' + +export interface AdapterClientInfo { + /** true when a full client bundle was emitted to dist/client/ */ + bundle: boolean + /** true when islands were built to dist/server/islands/ */ + islands: boolean +} + +export interface AdexRuntimeConfig { + manifests: { server: object; client: object } + paths: { assets: string; islands: string; client: string } + client: AdapterClientInfo +} + +export interface NodeAdapterOptions { + port?: number | string + host?: string +} + +export interface AdapterConfig { + /** npm package name — added to ssr.noExternal so it bundles into the server output */ + name: string + /** + * Returns a Vite plugin that handles dev-mode request serving. + * Called by the core adex() plugin with the same islands flag. + */ + devServerPlugin: (options: { islands: boolean }) => Plugin + /** + * Returns the source code string for the virtual:adex:server entry point. + * Core injects this verbatim — all runtime bootstrap logic lives here. + */ + serverEntry: (options: { islands: boolean }) => string } -export const createServer: ({ port: number, host: string }) => ServerOut +/** + * Adapter factory — pass to adex({ adapter: node() }) in vite.config.js + */ +export declare function node(options?: NodeAdapterOptions): AdapterConfig + +/** + * Runtime server factory — called by the generated virtual:adex:server entry + */ +export declare const createServer: (options?: { + port?: number | string + host?: string + adex?: AdexRuntimeConfig +}) => { run: () => void; fetch: undefined } diff --git a/packages/adapters/node/lib/index.js b/packages/adapters/node/lib/index.js index efe2f1b..a6a9e97 100644 --- a/packages/adapters/node/lib/index.js +++ b/packages/adapters/node/lib/index.js @@ -1,20 +1,89 @@ import { existsSync } from 'node:fs' import http from 'node:http' +import { createNodeDevServerPlugin } from './dev.js' +import { nodeRequestToFetch, fetchResponseToNode } from 'adex/http' -import { sirv, useMiddleware } from 'adex/ssr' +let islandMode = false + +/** + * Adapter factory — pass to adex({ adapter: node() }) in vite.config.js + * Returns an AdapterConfig object the core framework uses at build time. + * @param {import('./index.d.ts').NodeAdapterOptions} [options] + * @returns {import('./index.d.ts').AdapterConfig} + */ +export function node(options = {}) { + return { + name: 'adex-adapter-node', + devServerPlugin({ islands }) { + return createNodeDevServerPlugin({ islands }) + }, + serverEntry({ islands }) { + return `import { createServer } from 'adex-adapter-node' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { readFileSync } from 'node:fs' +import { env } from 'adex/env' -import { handler } from 'virtual:adex:handler' +import 'virtual:adex:font.css' +import 'virtual:adex:global.css' -let islandMode = false +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const PORT = parseInt(env.get('PORT', '3000'), 10) +const HOST = env.get('HOST', 'localhost') + +function readJSON(p) { + try { return JSON.parse(readFileSync(p, 'utf8')) } catch { return {} } +} + +const adexManifest = readJSON(join(__dirname, 'adex.manifest.json')) +const serverManifest = readJSON(join(__dirname, 'manifest.json')) +const clientManifest = adexManifest?.client?.bundle + ? readJSON(join(join(__dirname, adexManifest.client.manifestPath))) + : {} + +const paths = { + assets: join(__dirname, './assets'), + islands: join(__dirname, './islands'), + client: join(__dirname, adexManifest?.client?.outDir ?? '../client'), +} + +const server = createServer({ + port: PORT, + host: HOST, + adex: { + manifests: { server: serverManifest, client: clientManifest }, + paths, + client: adexManifest?.client ?? { bundle: false, islands: false }, + }, +}) + +if ('run' in server) { server.run() } +export default server.fetch +` + }, + } +} + +/** + * @param {{ manifests: object, paths: object, client: import('./index.d.ts').AdapterClientInfo }} adexConfig + */ +async function createHandler({ + manifests, + paths, + client = { bundle: true, islands: false }, +}) { + const { sirv, useMiddleware } = await import('adex/ssr') + // @ts-expect-error injected by vite + const { handler } = await import('virtual:adex:handler') -function createHandler({ manifests, paths }) { const serverAssets = sirv(paths.assets, { maxAge: 31536000, immutable: true, onNoMatch: defaultHandler, }) - let islandsWereGenerated = existsSync(paths.islands) + let islandsWereGenerated = client.islands && existsSync(paths.islands) // @ts-ignore let islandAssets = (req, res, next) => { @@ -30,7 +99,7 @@ function createHandler({ manifests, paths }) { }) } - let clientWasGenerated = existsSync(paths.client) + let clientWasGenerated = client.bundle && existsSync(paths.client) // @ts-ignore let clientAssets = (req, res, next) => { @@ -46,19 +115,24 @@ function createHandler({ manifests, paths }) { } async function defaultHandler(req, res) { - const { html: template, pageRoute, serverHandler } = await handler(req, res) - if (serverHandler) { - return serverHandler(req, res) + const fetchRequest = await nodeRequestToFetch(req) + const response = await handler(fetchRequest) + const pageRoute = response.headers.get('x-adex-page-route') + + if (!pageRoute) { + // API response or 404 — write directly + await fetchResponseToNode(response, res) + return } - const templateWithDeps = addDependencyAssets( - template, + // Page response — inject manifest CSS/JS assets + const html = await response.text() + const finalHTML = addDependencyAssets( + html, pageRoute, manifests.server, manifests.client ) - - const finalHTML = templateWithDeps res.setHeader('content-type', 'text/html') res.write(finalHTML) res.end() @@ -88,40 +162,11 @@ function createHandler({ manifests, paths }) { ) } -// function parseManifest(manifestString) { -// try { -// const manifestJSON = JSON.parse(manifestString) -// return manifestJSON -// } catch (err) { -// return {} -// } -// } - -// function getServerManifest() { -// const manifestPath = join(__dirname, 'manifest.json') -// if (existsSync(manifestPath)) { -// const manifestFile = readFileSync(manifestPath, 'utf8') -// return parseManifest(manifestFile) -// } -// return {} -// } - -// function getClientManifest() { -// const manifestPath = join(__dirname, '../client/manifest.json') -// if (existsSync(manifestPath)) { -// const manifestFile = readFileSync(manifestPath, 'utf8') -// return parseManifest(manifestFile) -// } -// return {} -// } - function manifestToHTML(manifest, filePath) { let links = [] let scripts = [] - // TODO: move it up the chain const rootServerFile = 'virtual:adex:server' - // if root manifest, also add it's css imports in if (manifest[rootServerFile]) { const graph = manifest[rootServerFile] links = links.concat( @@ -135,9 +180,7 @@ function manifestToHTML(manifest, filePath) { ) } - // TODO: move it up the chain const rootClientFile = 'virtual:adex:client' - // if root manifest, also add it's css imports in if (!islandMode && manifest[rootClientFile]) { const graph = manifest[rootClientFile] links = links.concat( @@ -229,14 +272,24 @@ export const createServer = ({ client: {}, }, paths: {}, + client: { bundle: true, islands: false }, }, } = {}) => { - const handler = createHandler(adex) - const server = http.createServer(handler) + // createHandler is async (uses dynamic imports); wrap in a lazy-init server + let server + + async function getServer() { + if (!server) { + const handler = await createHandler(adex) + server = http.createServer(handler) + } + return server + } return { - run() { - return server.listen(port, host, () => { + async run() { + const s = await getServer() + return s.listen(port, host, () => { console.log(`Listening on ${host}:${port}`) }) }, diff --git a/packages/adapters/node/package.json b/packages/adapters/node/package.json index 97bf7e2..28b99aa 100644 --- a/packages/adapters/node/package.json +++ b/packages/adapters/node/package.json @@ -38,5 +38,8 @@ "funding": { "type": "github", "url": "https://github.com/sponsors/barelyhuman" + }, + "dependencies": { + "adex": "workspace:*" } } diff --git a/playground-deno/.gitignore b/playground-deno/.gitignore new file mode 100644 index 0000000..c4beedd --- /dev/null +++ b/playground-deno/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env* +!.env.example + +/.islands \ No newline at end of file diff --git a/playground-deno/package.json b/playground-deno/package.json new file mode 100644 index 0000000..10b6eff --- /dev/null +++ b/playground-deno/package.json @@ -0,0 +1,32 @@ +{ + "name": "playground-deno", + "private": true, + "version": "0.0.21", + "engines": { + "node": ">=18.0.0" + }, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@preact/signals": "^1.3.0", + "adex-adapter-deno": "workspace:*", + "preact": "^10.24.2" + }, + "devDependencies": { + "@barelyhuman/prettier-config": "^1.1.0", + "@preact/preset-vite": "catalog:", + "@tailwindcss/forms": "^0.5.11", + "@types/node": "^20.19.39", + "adex": "workspace:*", + "autoprefixer": "^10.4.27", + "postcss": "^8.5.9", + "prettier": "^3.8.1", + "tailwindcss": "^3.4.19", + "vite": "^6.4.2" + }, + "prettier": "@barelyhuman/prettier-config" +} diff --git a/playground-deno/postcss.config.js b/playground-deno/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/playground-deno/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/playground-deno/public/vite.svg b/playground-deno/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/playground-deno/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/playground-deno/src/_app.jsx b/playground-deno/src/_app.jsx new file mode 100644 index 0000000..3968381 --- /dev/null +++ b/playground-deno/src/_app.jsx @@ -0,0 +1,19 @@ +import { App as AdexApp } from 'adex/app' +import { h } from 'preact' +import { hydrate as preactHydrate } from 'adex/router' + +export { prerender } from 'adex/app' + +import 'virtual:adex:global.css' + +export const App = ({ url }) => { + return +} + +async function hydrate() { + preactHydrate(h(App, null), document.getElementById('app')) +} + +if (typeof window !== 'undefined') { + hydrate() +} diff --git a/playground-deno/src/api/$id/hello.js b/playground-deno/src/api/$id/hello.js new file mode 100644 index 0000000..fd515b3 --- /dev/null +++ b/playground-deno/src/api/$id/hello.js @@ -0,0 +1,11 @@ +/** + * @param {Request} request + */ +export default request => { + const { pathname } = new URL(request.url) + // route: /api/:id/hello — id is the second path segment + const id = pathname.split('/')[2] + return new Response(`Hello from ${id}`, { + headers: { 'content-type': 'text/plain' }, + }) +} diff --git a/playground-deno/src/api/$id/json.js b/playground-deno/src/api/$id/json.js new file mode 100644 index 0000000..34f36d4 --- /dev/null +++ b/playground-deno/src/api/$id/json.js @@ -0,0 +1,9 @@ +/** + * @param {Request} request + */ +export default request => { + const { pathname } = new URL(request.url) + // route: /api/:id/json — id is the second path segment + const id = pathname.split('/')[2] + return Response.json({ message: `Hello in ${id}` }) +} diff --git a/playground-deno/src/api/hello.js b/playground-deno/src/api/hello.js new file mode 100644 index 0000000..5d2f9e4 --- /dev/null +++ b/playground-deno/src/api/hello.js @@ -0,0 +1,11 @@ +import { env } from 'adex/env' + +/** + * @param {Request} request + */ +export default request => { + return Response.json({ + pong: true, + appUrl: env.get('APP_URL'), + }) +} diff --git a/playground-deno/src/api/html.js b/playground-deno/src/api/html.js new file mode 100644 index 0000000..bf77345 --- /dev/null +++ b/playground-deno/src/api/html.js @@ -0,0 +1,14 @@ +import { afterAPICall } from 'adex/hook' + +afterAPICall(ctx => { + console.log('called after api') +}) + +/** + * @param {Request} request + */ +export default request => { + return new Response(`

Html Response

`, { + headers: { 'content-type': 'text/html' }, + }) +} diff --git a/playground-deno/src/api/random-data.js b/playground-deno/src/api/random-data.js new file mode 100644 index 0000000..0cb2f18 --- /dev/null +++ b/playground-deno/src/api/random-data.js @@ -0,0 +1,10 @@ +/** + * @param {Request} request + */ +export default function (request) { + return Response.json( + Array.from({ length: 3 }) + .fill(0) + .map((d, i) => (i + 1) * Math.random()) + ) +} diff --git a/playground-deno/src/api/status-helpers.js b/playground-deno/src/api/status-helpers.js new file mode 100644 index 0000000..5ac563b --- /dev/null +++ b/playground-deno/src/api/status-helpers.js @@ -0,0 +1,50 @@ +/** + * @param {Request} request + */ +export default request => { + const { searchParams } = new URL(request.url) + const type = searchParams.get('type') + const message = searchParams.get('message') + const errorBody = msg => (msg ? JSON.stringify({ error: msg }) : null) + const jsonHeaders = { 'content-type': 'application/json' } + + switch (type) { + case 'badRequest': + return new Response(errorBody(message), { + status: 400, + headers: jsonHeaders, + }) + case 'unauthorized': + return new Response(errorBody(message), { + status: 401, + headers: jsonHeaders, + }) + case 'forbidden': + return new Response(errorBody(message), { + status: 403, + headers: jsonHeaders, + }) + case 'notFound': + return new Response(errorBody(message), { + status: 404, + headers: jsonHeaders, + }) + case 'internalServerError': + return new Response(errorBody(message), { + status: 500, + headers: jsonHeaders, + }) + default: + return Response.json({ + usage: + 'Add ?type=badRequest&message=Custom%20message to test status helpers', + available: [ + 'badRequest', + 'unauthorized', + 'forbidden', + 'notFound', + 'internalServerError', + ], + }) + } +} diff --git a/playground-deno/src/assets/preact.svg b/playground-deno/src/assets/preact.svg new file mode 100644 index 0000000..908f17d --- /dev/null +++ b/playground-deno/src/assets/preact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/playground-deno/src/components/FormIsland.tsx b/playground-deno/src/components/FormIsland.tsx new file mode 100644 index 0000000..fdb7e12 --- /dev/null +++ b/playground-deno/src/components/FormIsland.tsx @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'preact/hooks' + +export function ListIsland() { + const [data, setData] = useState([]) + + useEffect(() => { + setData([1, 2, 3]) + }, []) + + return ( +
+
    + {data.map(d => ( +
  • {d}
  • + ))} +
+
+ ) +} diff --git a/playground-deno/src/components/ListIsland.tsx b/playground-deno/src/components/ListIsland.tsx new file mode 100644 index 0000000..0f1a762 --- /dev/null +++ b/playground-deno/src/components/ListIsland.tsx @@ -0,0 +1,14 @@ +export function FormIsland() { + const onSubmit = e => { + e.preventDefault() + alert('sumbitted') + } + return ( +
+
+ + +
+
+ ) +} diff --git a/playground-deno/src/components/SharedSignal.tsx b/playground-deno/src/components/SharedSignal.tsx new file mode 100644 index 0000000..115dc14 --- /dev/null +++ b/playground-deno/src/components/SharedSignal.tsx @@ -0,0 +1,34 @@ +import { signal } from '@preact/signals' +import { useEffect } from 'preact/hooks' + +const data$ = signal([]) + +async function fetchData() { + const data = await fetch('/api/random-data').then(d => d.json()) + data$.value = data +} + +export function Triggerer() { + useEffect(() => { + fetchData() + }, []) + return ( +
+

Triggerer Island

+ +
+ ) +} + +export function Renderer() { + return ( +
+

Renderer Island

+
    + {data$.value.map(d => ( +
  • {d}
  • + ))} +
+
+ ) +} diff --git a/playground-deno/src/components/counter.tsx b/playground-deno/src/components/counter.tsx new file mode 100644 index 0000000..5bb30ab --- /dev/null +++ b/playground-deno/src/components/counter.tsx @@ -0,0 +1,6 @@ +import { useState } from 'preact/hooks' + +export function Counter() { + const [count, setCount] = useState(0) + return +} diff --git a/playground-deno/src/global.css b/playground-deno/src/global.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/playground-deno/src/global.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/playground-deno/src/index.html b/playground-deno/src/index.html new file mode 100644 index 0000000..8ee2bb9 --- /dev/null +++ b/playground-deno/src/index.html @@ -0,0 +1,11 @@ + + + + + + Document + + +
+ + \ No newline at end of file diff --git a/playground-deno/src/lib/test-env.js b/playground-deno/src/lib/test-env.js new file mode 100644 index 0000000..0fbed76 --- /dev/null +++ b/playground-deno/src/lib/test-env.js @@ -0,0 +1,3 @@ +import { env } from 'adex/env' + +export const APP_URL = env.get('APP_URL') diff --git a/playground-deno/src/pages/$id/hello.tsx b/playground-deno/src/pages/$id/hello.tsx new file mode 100644 index 0000000..8cc9032 --- /dev/null +++ b/playground-deno/src/pages/$id/hello.tsx @@ -0,0 +1,3 @@ +export default ({ routeParams }) => { + return

Hello from {routeParams.id}

+} diff --git a/playground-deno/src/pages/about.tsx b/playground-deno/src/pages/about.tsx new file mode 100644 index 0000000..6bf8fbc --- /dev/null +++ b/playground-deno/src/pages/about.tsx @@ -0,0 +1,6 @@ +import { useTitle } from 'adex/head' + +export default () => { + useTitle('About Page') + return

About

+} diff --git a/playground-deno/src/pages/index.jsx b/playground-deno/src/pages/index.jsx new file mode 100644 index 0000000..47aa423 --- /dev/null +++ b/playground-deno/src/pages/index.jsx @@ -0,0 +1,42 @@ +import { useState } from 'preact/hooks' +import './local-index.css' +import { Counter } from '../components/counter.tsx' + +export default function Page() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + Preact

+
+ +

+ Edit src/app.jsx and save to test HMR +

+
+

+ Click on the Vite and Preact logos to learn more +

+

+ Here's an island{' '} + + + +

+
+ ) +} diff --git a/playground-deno/src/pages/islands-test.tsx b/playground-deno/src/pages/islands-test.tsx new file mode 100644 index 0000000..b29fc50 --- /dev/null +++ b/playground-deno/src/pages/islands-test.tsx @@ -0,0 +1,11 @@ +import { ListIsland } from '../components/FormIsland' +import { FormIsland } from '../components/ListIsland' + +export default function IslandsTest() { + return ( + <> + + + + ) +} diff --git a/playground-deno/src/pages/local-index.css b/playground-deno/src/pages/local-index.css new file mode 100644 index 0000000..a9cc5ca --- /dev/null +++ b/playground-deno/src/pages/local-index.css @@ -0,0 +1,3 @@ +body{ + background:red; +} \ No newline at end of file diff --git a/playground-deno/src/pages/shared-signal.tsx b/playground-deno/src/pages/shared-signal.tsx new file mode 100644 index 0000000..9f63c7b --- /dev/null +++ b/playground-deno/src/pages/shared-signal.tsx @@ -0,0 +1,10 @@ +import { Renderer, Triggerer } from '../components/SharedSignal.js' + +export default function SharedSignal() { + return ( +
+ + +
+ ) +} diff --git a/playground-deno/tailwind.config.js b/playground-deno/tailwind.config.js new file mode 100644 index 0000000..0e2ccbb --- /dev/null +++ b/playground-deno/tailwind.config.js @@ -0,0 +1,12 @@ +import forms from '@tailwindcss/forms' +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}'], + theme: { + extend: {}, + fontFamily: { + sans: 'Inter, sans-serif', + }, + }, + plugins: [forms], +} diff --git a/playground-deno/tsconfig.json b/playground-deno/tsconfig.json new file mode 100644 index 0000000..9db2d98 --- /dev/null +++ b/playground-deno/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "allowJs": true, + "noEmit": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "moduleResolution": "Node", + "paths": { + "adex": ["../adex/vite.js"], + "adex/*": ["../adex/src/*"] + } + } +} diff --git a/playground-deno/vite.config.js b/playground-deno/vite.config.js new file mode 100644 index 0000000..28581d1 --- /dev/null +++ b/playground-deno/vite.config.js @@ -0,0 +1,26 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' +import { adex } from 'adex' +import { deno } from 'adex-adapter-deno' +import { providers } from 'adex/fonts' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + adex({ + islands: false, + adapter: deno(), + fonts: { + providers: [providers.google()], + families: [ + { + name: 'Inter', + weights: ['400', '600'], + styles: ['normal'], + }, + ], + }, + }), + preact(), + ], +}) diff --git a/playground/src/api/$id/hello.js b/playground/src/api/$id/hello.js index b274d28..fd515b3 100644 --- a/playground/src/api/$id/hello.js +++ b/playground/src/api/$id/hello.js @@ -1,7 +1,11 @@ /** - * @param {import("adex/http").IncomingMessage} req - * @param {import("adex/http").ServerResponse} res + * @param {Request} request */ -export default (req, res) => { - return res.text(`Hello from ${req.params.id}`) +export default request => { + const { pathname } = new URL(request.url) + // route: /api/:id/hello — id is the second path segment + const id = pathname.split('/')[2] + return new Response(`Hello from ${id}`, { + headers: { 'content-type': 'text/plain' }, + }) } diff --git a/playground/src/api/$id/json.js b/playground/src/api/$id/json.js index a09c90d..34f36d4 100644 --- a/playground/src/api/$id/json.js +++ b/playground/src/api/$id/json.js @@ -1,9 +1,9 @@ /** - * @param {import("adex/http").IncomingMessage} req - * @param {import("adex/http").ServerResponse} res + * @param {Request} request */ -export default (req, res) => { - return res.json({ - message: `Hello in ${req.params.id}`, - }) +export default request => { + const { pathname } = new URL(request.url) + // route: /api/:id/json — id is the second path segment + const id = pathname.split('/')[2] + return Response.json({ message: `Hello in ${id}` }) } diff --git a/playground/src/api/hello.js b/playground/src/api/hello.js index c1ed97d..5d2f9e4 100644 --- a/playground/src/api/hello.js +++ b/playground/src/api/hello.js @@ -1,10 +1,10 @@ import { env } from 'adex/env' + /** - * @param {import("adex/http").IncomingMessage} req - * @param {import("adex/http").ServerResponse} res + * @param {Request} request */ -export default (req, res) => { - return res.json({ +export default request => { + return Response.json({ pong: true, appUrl: env.get('APP_URL'), }) diff --git a/playground/src/api/html.js b/playground/src/api/html.js index 291ce05..bf77345 100644 --- a/playground/src/api/html.js +++ b/playground/src/api/html.js @@ -5,9 +5,10 @@ afterAPICall(ctx => { }) /** - * @param {import("adex/http").IncomingMessage} req - * @param {import("adex/http").ServerResponse} res + * @param {Request} request */ -export default (req, res) => { - return res.html(`

Html Response

`) +export default request => { + return new Response(`

Html Response

`, { + headers: { 'content-type': 'text/html' }, + }) } diff --git a/playground/src/api/random-data.js b/playground/src/api/random-data.js index cb7e11f..0cb2f18 100644 --- a/playground/src/api/random-data.js +++ b/playground/src/api/random-data.js @@ -1,5 +1,8 @@ -export default function (req, res) { - return res.json( +/** + * @param {Request} request + */ +export default function (request) { + return Response.json( Array.from({ length: 3 }) .fill(0) .map((d, i) => (i + 1) * Math.random()) diff --git a/playground/src/api/status-helpers.js b/playground/src/api/status-helpers.js index 1f4f532..5ac563b 100644 --- a/playground/src/api/status-helpers.js +++ b/playground/src/api/status-helpers.js @@ -1,27 +1,50 @@ /** - * @param {import("adex/http").IncomingMessage} req - * @param {import("adex/http").ServerResponse} res + * @param {Request} request */ -export default (req, res) => { - const { pathname, searchParams } = new URL(req.url, 'http://localhost') +export default request => { + const { searchParams } = new URL(request.url) const type = searchParams.get('type') const message = searchParams.get('message') + const errorBody = msg => (msg ? JSON.stringify({ error: msg }) : null) + const jsonHeaders = { 'content-type': 'application/json' } switch (type) { case 'badRequest': - return res.badRequest(message) + return new Response(errorBody(message), { + status: 400, + headers: jsonHeaders, + }) case 'unauthorized': - return res.unauthorized(message) + return new Response(errorBody(message), { + status: 401, + headers: jsonHeaders, + }) case 'forbidden': - return res.forbidden(message) + return new Response(errorBody(message), { + status: 403, + headers: jsonHeaders, + }) case 'notFound': - return res.notFound(message) + return new Response(errorBody(message), { + status: 404, + headers: jsonHeaders, + }) case 'internalServerError': - return res.internalServerError(message) + return new Response(errorBody(message), { + status: 500, + headers: jsonHeaders, + }) default: - return res.json({ - usage: 'Add ?type=badRequest&message=Custom%20message to test status helpers', - available: ['badRequest', 'unauthorized', 'forbidden', 'notFound', 'internalServerError'] + return Response.json({ + usage: + 'Add ?type=badRequest&message=Custom%20message to test status helpers', + available: [ + 'badRequest', + 'unauthorized', + 'forbidden', + 'notFound', + 'internalServerError', + ], }) } -} \ No newline at end of file +} diff --git a/playground/vite.config.js b/playground/vite.config.js index 2ddcaeb..418da50 100644 --- a/playground/vite.config.js +++ b/playground/vite.config.js @@ -1,6 +1,7 @@ import { defineConfig } from 'vite' import preact from '@preact/preset-vite' import { adex } from 'adex' +import { node } from 'adex-adapter-node' import { providers } from 'adex/fonts' // https://vitejs.dev/config/ @@ -8,6 +9,7 @@ export default defineConfig({ plugins: [ adex({ islands: false, + adapter: node(), fonts: { providers: [providers.google()], families: [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c21832..0a6d438 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,13 +79,13 @@ importers: version: 1.15.0 preact: specifier: ^10.22.0 - version: 10.24.2 + version: 10.29.1 preact-iso: specifier: ^2.9.0 - version: 2.9.0(preact-render-to-string@6.5.5(preact@10.24.2))(preact@10.24.2) + version: 2.9.0(preact-render-to-string@6.5.5(preact@10.29.1))(preact@10.29.1) preact-render-to-string: specifier: ^6.5.5 - version: 6.5.5(preact@10.24.2) + version: 6.5.5(preact@10.29.1) regexparam: specifier: ^3.0.0 version: 3.0.0 @@ -107,16 +107,16 @@ importers: version: 1.1.0 '@preact/preset-vite': specifier: 'catalog:' - version: 2.10.5(@babel/core@7.24.7)(preact@10.24.2)(rollup@4.60.1)(vite@8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) + version: 2.10.5(@babel/core@7.24.7)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) '@types/node': specifier: ^20.14.10 - version: 20.16.10 + version: 20.19.39 adex-adapter-node: specifier: ^0.0.17 version: 0.0.17 autoprefixer: specifier: ^10.4.19 - version: 10.4.20(postcss@8.5.9) + version: 10.4.27(postcss@8.5.9) c8: specifier: 11.0.0 version: 11.0.0 @@ -134,13 +134,13 @@ importers: version: 0.8.0 prettier: specifier: ^3.5.3 - version: 3.5.3 + version: 3.8.1 tailwindcss: specifier: ^3.4.19 version: 3.4.19 vite: specifier: ^8.0.7 - version: 8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) + version: 8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) adex/tests/fixtures/minimal: dependencies: @@ -150,6 +150,9 @@ importers: adex: specifier: workspace:* version: link:../../.. + adex-adapter-node: + specifier: workspace:* + version: link:../../../../packages/adapters/node preact: specifier: 'catalog:' version: 10.24.2 @@ -166,6 +169,9 @@ importers: adex: specifier: workspace:* version: link:../../.. + adex-adapter-node: + specifier: workspace:* + version: link:../../../../packages/adapters/node preact: specifier: 'catalog:' version: 10.24.2 @@ -182,6 +188,9 @@ importers: adex: specifier: workspace:* version: link:../../.. + adex-adapter-node: + specifier: workspace:* + version: link:../../../../packages/adapters/node preact: specifier: 'catalog:' version: 10.24.2 @@ -205,7 +214,17 @@ importers: specifier: ^1.15.0 version: 1.15.0 - packages/adapters/node: {} + packages/adapters/deno: + dependencies: + adex: + specifier: workspace:* + version: link:../../../adex + + packages/adapters/node: + dependencies: + adex: + specifier: workspace:* + version: link:../../../adex playground: dependencies: @@ -1323,9 +1342,6 @@ packages: '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==, tarball: https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz} - '@types/node@20.16.10': - resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==, tarball: https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz} - '@types/node@20.19.39': resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==, tarball: https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz} @@ -1430,13 +1446,6 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==, tarball: https://registry.npmjs.org/astring/-/astring-1.9.0.tgz} hasBin: true - autoprefixer@10.4.20: - resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==, tarball: https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - autoprefixer@10.4.27: resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==, tarball: https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz} engines: {node: ^10 || ^12 || >=14} @@ -1480,11 +1489,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, tarball: https://registry.npmjs.org/braces/-/braces-3.0.3.tgz} engines: {node: '>=8'} - browserslist@4.24.0: - resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - browserslist@4.28.2: resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1517,9 +1521,6 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==, tarball: https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz} engines: {node: '>= 6'} - caniuse-lite@1.0.30001667: - resolution: {integrity: sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz} - caniuse-lite@1.0.30001787: resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz} @@ -1632,15 +1633,6 @@ packages: engines: {node: '>=4'} hasBin: true - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==, tarball: https://registry.npmjs.org/debug/-/debug-4.4.0.tgz} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, tarball: https://registry.npmjs.org/debug/-/debug-4.4.3.tgz} engines: {node: '>=6.0'} @@ -1710,9 +1702,6 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, tarball: https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz} - electron-to-chromium@1.5.32: - resolution: {integrity: sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.32.tgz} - electron-to-chromium@1.5.334: resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz} @@ -1743,10 +1732,6 @@ packages: engines: {node: '>=18'} hasBin: true - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==, tarball: https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz} - engines: {node: '>=6'} - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, tarball: https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz} engines: {node: '>=6'} @@ -1815,9 +1800,6 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==, tarball: https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz} engines: {node: '>=14'} - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==, tarball: https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz} - fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==, tarball: https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz} @@ -1878,10 +1860,6 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@13.0.0: - resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==, tarball: https://registry.npmjs.org/glob/-/glob-13.0.0.tgz} - engines: {node: 20 || >=22} - glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==, tarball: https://registry.npmjs.org/glob/-/glob-13.0.6.tgz} engines: {node: 18 || 20 || >=22} @@ -2174,10 +2152,6 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==, tarball: https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz} engines: {node: '>= 12.0.0'} - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==, tarball: https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz} - engines: {node: '>=14'} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==, tarball: https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz} engines: {node: '>=14'} @@ -2204,10 +2178,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz} - lru-cache@11.0.2: - resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz} - engines: {node: 20 || >=22} - lru-cache@11.3.3: resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz} engines: {node: 20 || >=22} @@ -2276,10 +2246,6 @@ packages: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==, tarball: https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz} engines: {node: '>=8'} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, tarball: https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz} - engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==, tarball: https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz} engines: {node: '>=16 || 14 >=14.17'} @@ -2343,9 +2309,6 @@ packages: node-html-parser@6.1.13: resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==, tarball: https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz} - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz} - node-releases@2.0.37: resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz} @@ -2366,10 +2329,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, tarball: https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz} engines: {node: '>=0.10.0'} - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==, tarball: https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz} - engines: {node: '>=0.10.0'} - npm-bundled@5.0.0: resolution: {integrity: sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==, tarball: https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz} engines: {node: ^20.17.0 || >=22.9.0} @@ -2493,17 +2452,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, tarball: https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz} - path-scurry@2.0.0: - resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==, tarball: https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz} - engines: {node: 20 || >=22} - path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==, tarball: https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz} engines: {node: 18 || 20 || >=22} - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==, tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz} @@ -2585,11 +2537,6 @@ packages: preact@10.29.1: resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==, tarball: https://registry.npmjs.org/preact/-/preact-10.29.1.tgz} - prettier@3.5.3: - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==, tarball: https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz} - engines: {node: '>=14'} - hasBin: true - prettier@3.8.1: resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==, tarball: https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz} engines: {node: '>=14'} @@ -2879,9 +2826,6 @@ packages: unconfig@7.5.0: resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==, tarball: https://registry.npmjs.org/unconfig/-/unconfig-7.5.0.tgz} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz} - undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz} @@ -2902,12 +2846,6 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, tarball: https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz} engines: {node: '>= 10.0.0'} - update-browserslist-db@1.1.0: - resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz} hasBin: true @@ -3170,7 +3108,7 @@ snapshots: dependencies: '@babel/compat-data': 7.24.7 '@babel/helper-validator-option': 7.24.7 - browserslist: 4.24.0 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -3540,7 +3478,7 @@ snapshots: '@isaacs/fs-minipass@4.0.1': dependencies: - minipass: 7.1.2 + minipass: 7.1.3 '@isaacs/string-locale-compare@1.1.0': {} @@ -3786,7 +3724,7 @@ snapshots: dependencies: '@npmcli/name-from-folder': 4.0.0 '@npmcli/package-json': 7.0.5 - glob: 13.0.0 + glob: 13.0.6 minimatch: 10.2.5 '@npmcli/metavuln-calculator@9.0.3': @@ -3806,7 +3744,7 @@ snapshots: '@npmcli/package-json@7.0.5': dependencies: '@npmcli/git': 7.0.2 - glob: 13.0.0 + glob: 13.0.6 hosted-git-info: 9.0.2 json-parse-even-better-errors: 5.0.0 proc-log: 6.1.0 @@ -3921,57 +3859,57 @@ snapshots: - rollup - supports-color - '@preact/preset-vite@2.10.5(@babel/core@7.24.7)(preact@10.24.2)(rollup@4.60.1)(vite@8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3))': + '@preact/preset-vite@2.10.5(@babel/core@7.24.7)(preact@10.24.2)(rollup@4.60.1)(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.24.7) - '@prefresh/vite': 2.4.12(preact@10.24.2)(vite@8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) + '@prefresh/vite': 2.4.12(preact@10.24.2)(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) '@rollup/pluginutils': 5.3.0(rollup@4.60.1) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.24.7) debug: 4.4.3 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) - vite-prerender-plugin: 0.5.13(vite@8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) + vite: 8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) + vite-prerender-plugin: 0.5.13(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact - rollup - supports-color - '@preact/preset-vite@2.10.5(@babel/core@7.24.7)(preact@10.24.2)(rollup@4.60.1)(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3))': + '@preact/preset-vite@2.10.5(@babel/core@7.24.7)(preact@10.29.1)(rollup@4.60.1)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.24.7) - '@prefresh/vite': 2.4.12(preact@10.24.2)(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) + '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) '@rollup/pluginutils': 5.3.0(rollup@4.60.1) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.24.7) debug: 4.4.3 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) - vite-prerender-plugin: 0.5.13(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + vite-prerender-plugin: 0.5.13(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact - rollup - supports-color - '@preact/preset-vite@2.10.5(@babel/core@7.24.7)(preact@10.29.1)(rollup@4.60.1)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': + '@preact/preset-vite@2.10.5(@babel/core@7.24.7)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.24.7) - '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) + '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) '@rollup/pluginutils': 5.3.0(rollup@4.60.1) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.24.7) debug: 4.4.3 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) - vite-prerender-plugin: 0.5.13(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) + vite: 8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) + vite-prerender-plugin: 0.5.13(vite@8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact @@ -4009,7 +3947,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@prefresh/vite@2.4.12(preact@10.24.2)(vite@8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3))': + '@prefresh/vite@2.4.12(preact@10.24.2)(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3))': dependencies: '@babel/core': 7.24.7 '@prefresh/babel-plugin': 0.5.3 @@ -4017,23 +3955,23 @@ snapshots: '@prefresh/utils': 1.2.0 '@rollup/pluginutils': 4.2.1 preact: 10.24.2 - vite: 8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) + vite: 8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) transitivePeerDependencies: - supports-color - '@prefresh/vite@2.4.12(preact@10.24.2)(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3))': + '@prefresh/vite@2.4.12(preact@10.29.1)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.24.7 '@prefresh/babel-plugin': 0.5.3 - '@prefresh/core': 1.5.2(preact@10.24.2) + '@prefresh/core': 1.5.2(preact@10.29.1) '@prefresh/utils': 1.2.0 '@rollup/pluginutils': 4.2.1 - preact: 10.24.2 - vite: 8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) + preact: 10.29.1 + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - '@prefresh/vite@2.4.12(preact@10.29.1)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': + '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3))': dependencies: '@babel/core': 7.24.7 '@prefresh/babel-plugin': 0.5.3 @@ -4041,7 +3979,7 @@ snapshots: '@prefresh/utils': 1.2.0 '@rollup/pluginutils': 4.2.1 preact: 10.29.1 - vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + vite: 8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -4255,10 +4193,6 @@ snapshots: '@types/istanbul-lib-coverage@2.0.6': {} - '@types/node@20.16.10': - dependencies: - undici-types: 6.19.8 - '@types/node@20.19.39': dependencies: undici-types: 6.21.0 @@ -4308,7 +4242,7 @@ snapshots: agent-base@7.1.1: dependencies: - debug: 4.4.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -4339,16 +4273,6 @@ snapshots: astring@1.9.0: {} - autoprefixer@10.4.20(postcss@8.5.9): - dependencies: - browserslist: 4.24.0 - caniuse-lite: 1.0.30001667 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.0 - postcss: 8.5.9 - postcss-value-parser: 4.2.0 - autoprefixer@10.4.27(postcss@8.5.9): dependencies: browserslist: 4.28.2 @@ -4388,13 +4312,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.0: - dependencies: - caniuse-lite: 1.0.30001667 - electron-to-chromium: 1.5.32 - node-releases: 2.0.18 - update-browserslist-db: 1.1.0(browserslist@4.24.0) - browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.16 @@ -4435,9 +4352,9 @@ snapshots: dependencies: '@npmcli/fs': 5.0.0 fs-minipass: 3.0.3 - glob: 13.0.0 + glob: 13.0.6 lru-cache: 11.3.3 - minipass: 7.1.2 + minipass: 7.1.3 minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 @@ -4446,8 +4363,6 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001667: {} - caniuse-lite@1.0.30001787: {} chokidar@3.6.0: @@ -4572,10 +4487,6 @@ snapshots: cssesc@3.0.0: {} - debug@4.4.0: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -4626,8 +4537,6 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.32: {} - electron-to-chromium@1.5.334: {} emoji-regex@10.4.0: {} @@ -4699,8 +4608,6 @@ snapshots: '@esbuild/win32-x64': 0.27.2 optional: true - escalade@3.1.2: {} - escalade@3.2.0: {} estree-walker@2.0.2: {} @@ -4779,8 +4686,6 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fraction.js@4.3.7: {} - fraction.js@5.3.4: {} fs-extra@11.3.4: @@ -4791,7 +4696,7 @@ snapshots: fs-minipass@3.0.3: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 fsevents@2.3.3: optional: true @@ -4821,7 +4726,7 @@ snapshots: glob-bin@1.0.0: dependencies: foreground-child: 3.3.1 - glob: 13.0.0 + glob: 13.0.6 jackspeak: 4.1.1 package-json-from-dist: 1.0.1 @@ -4838,15 +4743,9 @@ snapshots: foreground-child: 3.3.1 jackspeak: 4.1.1 minimatch: 10.2.5 - minipass: 7.1.2 + minipass: 7.1.3 package-json-from-dist: 1.0.1 - path-scurry: 2.0.0 - - glob@13.0.0: - dependencies: - minimatch: 10.2.5 - minipass: 7.1.2 - path-scurry: 2.0.0 + path-scurry: 2.0.2 glob@13.0.6: dependencies: @@ -4896,14 +4795,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.4.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 - debug: 4.4.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -5090,8 +4989,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 - lilconfig@3.1.2: {} - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -5112,8 +5009,6 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.0.2: {} - lru-cache@11.3.3: {} lru-cache@5.1.1: @@ -5135,7 +5030,7 @@ snapshots: '@npmcli/redact': 4.0.0 cacache: 20.0.4 http-cache-semantics: 4.1.1 - minipass: 7.1.2 + minipass: 7.1.3 minipass-fetch: 5.0.2 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 @@ -5166,11 +5061,11 @@ snapshots: minipass-collect@2.0.1: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 minipass-fetch@5.0.2: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 minipass-sized: 2.0.0 minizlib: 3.1.0 optionalDependencies: @@ -5186,19 +5081,17 @@ snapshots: minipass-sized@2.0.0: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 minipass@3.3.6: dependencies: yallist: 4.0.0 - minipass@7.1.2: {} - minipass@7.1.3: {} minizlib@3.1.0: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 mri@1.2.0: {} @@ -5252,8 +5145,6 @@ snapshots: css-select: 5.1.0 he: 1.2.0 - node-releases@2.0.18: {} - node-releases@2.0.37: {} node-stream-zip@1.15.0: {} @@ -5270,8 +5161,6 @@ snapshots: normalize-path@3.0.0: {} - normalize-range@0.1.2: {} - npm-bundled@5.0.0: dependencies: npm-normalize-package-bin: 5.0.0 @@ -5306,7 +5195,7 @@ snapshots: '@npmcli/redact': 4.0.0 jsonparse: 1.3.1 make-fetch-happen: 15.0.5 - minipass: 7.1.2 + minipass: 7.1.3 minipass-fetch: 5.0.2 minizlib: 3.1.0 npm-package-arg: 13.0.2 @@ -5374,7 +5263,7 @@ snapshots: '@npmcli/run-script': 10.0.4 cacache: 20.0.4 fs-minipass: 3.0.3 - minipass: 7.1.2 + minipass: 7.1.3 npm-package-arg: 13.0.2 npm-packlist: 10.0.4 npm-pick-manifest: 11.0.3 @@ -5411,18 +5300,11 @@ snapshots: path-parse@1.0.7: {} - path-scurry@2.0.0: - dependencies: - lru-cache: 11.0.2 - minipass: 7.1.2 - path-scurry@2.0.2: dependencies: lru-cache: 11.3.3 minipass: 7.1.3 - picocolors@1.1.0: {} - picocolors@1.1.1: {} picomatch@4.0.4: {} @@ -5449,7 +5331,7 @@ snapshots: postcss-load-config@4.0.2(postcss@8.5.9): dependencies: - lilconfig: 3.1.2 + lilconfig: 3.1.3 yaml: 2.8.3 optionalDependencies: postcss: 8.5.9 @@ -5477,21 +5359,19 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - preact-iso@2.9.0(preact-render-to-string@6.5.5(preact@10.24.2))(preact@10.24.2): + preact-iso@2.9.0(preact-render-to-string@6.5.5(preact@10.29.1))(preact@10.29.1): dependencies: - preact: 10.24.2 - preact-render-to-string: 6.5.5(preact@10.24.2) + preact: 10.29.1 + preact-render-to-string: 6.5.5(preact@10.29.1) - preact-render-to-string@6.5.5(preact@10.24.2): + preact-render-to-string@6.5.5(preact@10.29.1): dependencies: - preact: 10.24.2 + preact: 10.29.1 preact@10.24.2: {} preact@10.29.1: {} - prettier@3.5.3: {} - prettier@3.8.1: {} pretty-format@29.7.0: @@ -5649,7 +5529,7 @@ snapshots: socks-proxy-agent@8.0.4: dependencies: agent-base: 7.1.1 - debug: 4.4.0 + debug: 4.4.3 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -5692,7 +5572,7 @@ snapshots: ssri@13.0.1: dependencies: - minipass: 7.1.2 + minipass: 7.1.3 stack-trace@1.0.0-pre2: {} @@ -5771,7 +5651,7 @@ snapshots: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 - minipass: 7.1.2 + minipass: 7.1.3 minizlib: 3.1.0 yallist: 5.0.0 @@ -5841,8 +5721,6 @@ snapshots: quansync: 1.0.0 unconfig-core: 7.5.0 - undici-types@6.19.8: {} - undici-types@6.20.0: optional: true @@ -5862,12 +5740,6 @@ snapshots: universalify@2.0.1: {} - update-browserslist-db@1.1.0(browserslist@4.24.0): - dependencies: - browserslist: 4.24.0 - escalade: 3.1.2 - picocolors: 1.1.1 - update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -5909,7 +5781,7 @@ snapshots: stack-trace: 1.0.0-pre2 vite: 6.4.2(@types/node@22.13.16)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) - vite-prerender-plugin@0.5.13(vite@8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)): + vite-prerender-plugin@0.5.13(vite@8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -5917,7 +5789,7 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.4 stack-trace: 1.0.0-pre2 - vite: 8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) + vite: 8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3) vite-prerender-plugin@0.5.13(vite@8.0.7(@types/node@22.13.16)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3)): dependencies: @@ -5959,7 +5831,7 @@ snapshots: lightningcss: 1.32.0 yaml: 2.8.3 - vite@8.0.7(@types/node@20.16.10)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3): + vite@8.0.7(@types/node@20.19.39)(esbuild@0.27.2)(jiti@2.6.1)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -5967,7 +5839,7 @@ snapshots: rolldown: 1.0.0-rc.13 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 20.16.10 + '@types/node': 20.19.39 esbuild: 0.27.2 fsevents: 2.3.3 jiti: 2.6.1 @@ -6066,7 +5938,7 @@ snapshots: yargs@18.0.0: dependencies: cliui: 9.0.1 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 string-width: 7.2.0 y18n: 5.0.8 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6541f37..d24bcc7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,6 +6,7 @@ packages: - adex - create-adex - playground + - playground-* - packages/**/* - adex/tests/**/*