Press: Implement hot-reload & caching layer

This commit is contained in:
Fuma Nama
2026-03-04 00:48:19 +08:00
parent 018d9b704b
commit 0379c741bb
19 changed files with 663 additions and 179 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumapress': patch
---
Implement hot-reload & caching layer
+7 -1
View File
@@ -33,14 +33,19 @@
"types:check": "tsc --noEmit"
},
"dependencies": {
"@hono/node-server": "^1.19.10",
"@hono/node-ws": "^1.3.0",
"@takumi-rs/image-response": "^0.69.5",
"chokidar": "^5.0.0",
"commander": "^14.0.3",
"dotenv": "^17.3.1",
"fumadocs-core": "workspace:*",
"fumadocs-ui": "workspace:*",
"gray-matter": "^4.0.3",
"hast-util-to-jsx-runtime": "^2.3.6",
"hono": "^4.12.4",
"lucide-react": "^0.575.0",
"picomatch": "^4.0.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-server-dom-webpack": "^19.2.4",
@@ -51,7 +56,6 @@
"tinyglobby": "^0.2.15",
"unrun": "^0.2.28",
"vfile": "^6.0.3",
"vite": "^7.3.1",
"waku": "1.0.0-alpha.5",
"zod": "^4.3.6"
},
@@ -61,6 +65,7 @@
"@types/mdast": "^4.0.4",
"@types/mdx": "^2.0.13",
"@types/node": "^25.3.3",
"@types/picomatch": "^4.0.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/unist": "^3.0.3",
@@ -69,6 +74,7 @@
"tsdown": "0.20.3",
"typescript": "^5.9.3",
"unified": "^11.0.5",
"vite": "^7.3.1",
"vite-tsconfig-paths": "^6.1.1"
}
}
@@ -15,8 +15,10 @@ export async function runStart(flags: { host?: string; port?: string }) {
}
process.env.PORT = String(port);
process.env.PROJECT_DIR = process.cwd();
process.env.HOT_RELOAD = '1';
process.chdir(baseDir);
await import(serveFileUrl);
console.log(`ready: Listening on http://${host || 'localhost'}:${port}/`);
}
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { program } from 'commander';
import { runStart } from './utils.js';
import { runStart } from './commands.js';
import fs from 'node:fs/promises';
import { findConfigPath } from '@/config/load-node.js';
-16
View File
@@ -1,25 +1,9 @@
import * as dotenv from 'dotenv';
import * as vite from 'vite';
import { defineConfig, type FumapressConfig } from '@/config/global';
import { checkConfig, findConfigPath } from '@/config/load-node';
import { pathToFileURL } from 'node:url';
export function loadEnv() {
dotenv.config({ path: ['.env.local', '.env'], quiet: true });
}
export async function loadConfig(): Promise<FumapressConfig> {
const configPath = await findConfigPath();
if (configPath !== null) {
const imported = await vite.runnerImport<{ default: FumapressConfig }>(
pathToFileURL(configPath).href,
);
return checkConfig(configPath, imported.module.default);
}
return defineConfig();
}
export function overrideNodeEnv(nodeEnv: 'development' | 'production') {
// set NODE_ENV before runnerImport https://github.com/vitejs/vite/issues/20299
if (process.env.NODE_ENV && process.env.NODE_ENV !== nodeEnv) {
@@ -0,0 +1,29 @@
'use client';
import { decodeEvent } from '@/lib/waku/hot-reload';
import { useEffect } from 'react';
import { useRouter } from 'waku';
export function HotReload() {
const router = useRouter();
useEffect(() => {
const url = new URL('/_ws', window.location.href);
url.protocol = url.protocol === 'https' ? 'wss' : 'ws';
const socket = new WebSocket(url);
socket.addEventListener('message', (event) => {
const decoded = decodeEvent(event.data);
if (!decoded) return;
console.log('Received:', decoded);
if (decoded.type === 'revalidate') {
void router.reload();
}
});
return () => {
socket.close();
};
}, []);
return <></>;
}
+18 -3
View File
@@ -8,7 +8,14 @@ export interface RevalidableConfig<Args extends unknown[], O> {
create: (...args: Args) => O;
}
export function revalidable<Args extends unknown[], O>(config: RevalidableConfig<Args, O>) {
export type WithRevalidate<Args extends unknown[], O> = {
revalidate: (keepStale?: boolean) => void;
(...args: Args): O;
};
export function revalidable<Args extends unknown[], O>(
config: RevalidableConfig<Args, O>,
): WithRevalidate<Args, O> {
const { create, staleTime } = config;
if (typeof staleTime === 'number') {
@@ -16,7 +23,7 @@ export function revalidable<Args extends unknown[], O>(config: RevalidableConfig
let lastResult: O = undefined as O;
let revalidating = false;
return function (...args: Args) {
const out: WithRevalidate<Args, O> = function (...args: Args) {
if (lastValidated === null) {
// init
lastResult = create(...args);
@@ -46,7 +53,15 @@ export function revalidable<Args extends unknown[], O>(config: RevalidableConfig
return lastResult;
};
out.revalidate = (keepStale = true) => {
lastValidated = keepStale ? 0 : null;
};
}
return cache(create);
const out = cache(create) as WithRevalidate<Args, O>;
out.revalidate = () => {
return;
};
return out;
}
-138
View File
@@ -1,138 +0,0 @@
import { InferPageType, loader, source } from 'fumadocs-core/source';
import { glob } from 'tinyglobby';
import grayMatter from 'gray-matter';
import fs from 'node:fs/promises';
import { metaSchema, pageSchema } from 'fumadocs-core/source/schema';
import { getConfigRuntime } from '../config/load-runtime';
import path from 'node:path';
import { revalidable } from './revalidable';
import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons';
import type { ContentConfig, ProjectConfig } from '@/config/global';
import { normalizeProjects } from './source/config';
export interface RawPage {
type: 'page';
path: string;
absolutePath: string;
data: {
title: string;
description?: string;
content: string;
frontmatter: Record<string, unknown>;
project: ProjectConfig;
};
}
export interface RawMeta {
type: 'meta';
path: string;
absolutePath: string;
data: {
title?: string;
description?: string;
} & Record<string, unknown>;
}
async function getPages(config: ContentConfig): Promise<{
pages: RawPage[];
metas: RawMeta[];
}> {
async function md(project: ProjectConfig, file: string): Promise<RawPage> {
const absolutePath = path.resolve(project.dir, file);
const content = (await fs.readFile(absolutePath)).toString();
const parsed = grayMatter({ content });
// use default frontmatter if invalid in Fumadocs' spec
const { data: frontmatter = {} } = pageSchema.partial().loose().safeParse(parsed.data);
return {
type: 'page',
path: file,
absolutePath,
data: {
title: frontmatter.title ?? path.basename(file, path.extname(file)),
description: frontmatter.description,
content: parsed.content,
frontmatter,
project,
},
};
}
async function json(project: ProjectConfig, file: string): Promise<RawMeta | undefined> {
const absolutePath = path.resolve(project.dir, file);
const content = (await fs.readFile(absolutePath)).toString();
const parsed = JSON.parse(content);
const result = metaSchema.loose().safeParse(parsed);
// ignore if it is not `meta.json` for Fumadocs
if (result.error) return;
return {
type: 'meta',
path: file,
absolutePath,
data: result.data,
};
}
async function project(project: ProjectConfig) {
const files = await glob(
project.include ?? ['**/*.{md,mdx}', '**/meta.json', '!node_modules'],
{
cwd: project.dir,
},
);
return await Promise.all(
files.map(async (file) => {
const ext = path.extname(file);
try {
switch (ext) {
case '.json':
return await json(project, file);
case '.mdx':
case '.md':
return await md(project, file);
}
} catch (e) {
console.error(`error when parsing ${file}`, e);
}
}),
);
}
const projects = normalizeProjects(config.projects ?? [{ dir: '', name: 'root' }]);
const all = (await Promise.all(projects.map(project))).flat();
return {
metas: all.filter((item) => item?.type === 'meta'),
pages: all.filter((item) => item?.type === 'page'),
};
}
export const getSource = revalidable({
async create() {
const config = await getConfigRuntime();
return loader({
source: source(await getPages(config.content ?? {})),
plugins: [lucideIconsPlugin()],
baseUrl: '/',
});
},
});
export type SourcePage = InferPageType<Awaited<ReturnType<typeof getSource>>>;
export function getPageImage(slugs: string[]) {
const segments = [...slugs, 'image.webp'];
return {
segments,
url: `/og/${segments.join('/')}`,
};
}
+8 -1
View File
@@ -1,11 +1,18 @@
import type { ProjectConfig } from '@/config';
import path from 'node:path';
export function normalizeProjects(projects: ProjectConfig[]): ProjectConfig[] {
export type NormalizedProjectConfig = Required<ProjectConfig>;
export const DefaultProjects: ProjectConfig[] = [{ dir: '', name: 'root' }];
export function normalizeProjects(
projects: ProjectConfig[] = DefaultProjects,
): NormalizedProjectConfig[] {
const baseDir = process.env.PROJECT_DIR ?? process.cwd();
return projects.map((project) => ({
...project,
include: project.include ?? ['**/*.{md,mdx}', '**/meta.json', '!**/node_modules'],
dir: path.resolve(baseDir, project.dir),
}));
}
+29
View File
@@ -0,0 +1,29 @@
import { type InferPageType, loader, source } from 'fumadocs-core/source';
import { getConfigRuntime } from '@/config/load-runtime';
import { revalidable } from '@/lib/revalidable';
import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons';
import { getPages } from './storage';
export const getSource = revalidable({
async create() {
const config = await getConfigRuntime();
return loader({
source: source(await getPages(config.content ?? {})),
plugins: [lucideIconsPlugin()],
baseUrl: '/',
});
},
staleTime: 60 * 1000,
});
export type SourcePage = InferPageType<Awaited<ReturnType<typeof getSource>>>;
export function getPageImage(slugs: string[]) {
const segments = [...slugs, 'image.webp'];
return {
segments,
url: `/og/${segments.join('/')}`,
};
}
+129
View File
@@ -0,0 +1,129 @@
import type { ContentConfig } from '@/config/global';
import fs from 'node:fs/promises';
import { glob } from 'tinyglobby';
import grayMatter from 'gray-matter';
import path from 'node:path';
import { metaSchema, pageSchema } from 'fumadocs-core/source/schema';
import { type NormalizedProjectConfig, normalizeProjects } from './config';
export interface RawPage {
type: 'page';
path: string;
absolutePath: string;
data: {
title: string;
description?: string;
content: string;
frontmatter: Record<string, unknown>;
project: NormalizedProjectConfig;
};
}
export interface RawMeta {
type: 'meta';
path: string;
absolutePath: string;
data: {
title?: string;
description?: string;
} & Record<string, unknown>;
}
export const filesCache = new Map<string, RawPage | RawMeta>();
async function md(
project: NormalizedProjectConfig,
absolutePath: string,
file: string,
): Promise<RawPage> {
const content = (await fs.readFile(absolutePath)).toString();
const parsed = grayMatter({ content });
// use default frontmatter if invalid in Fumadocs' spec
const { data: frontmatter = {} } = pageSchema.partial().loose().safeParse(parsed.data);
return {
type: 'page',
path: file,
absolutePath,
data: {
title: frontmatter.title ?? path.basename(file, path.extname(file)),
description: frontmatter.description,
content: parsed.content,
frontmatter,
project,
},
};
}
async function json(
_project: NormalizedProjectConfig,
absolutePath: string,
file: string,
): Promise<RawMeta | undefined> {
const content = (await fs.readFile(absolutePath)).toString();
const parsed = JSON.parse(content);
const result = metaSchema.loose().safeParse(parsed);
// ignore if it is not `meta.json` for Fumadocs
if (result.error) return;
return {
type: 'meta',
path: file,
absolutePath,
data: result.data,
};
}
export async function getPages(config: ContentConfig): Promise<{
pages: RawPage[];
metas: RawMeta[];
}> {
async function project(project: NormalizedProjectConfig) {
const files = await glob(project.include, {
cwd: project.dir,
});
return await Promise.all(
files.map(async (file) => {
const absolutePath = path.resolve(project.dir, file);
const cached = filesCache.get(absolutePath);
if (cached) return cached;
const ext = path.extname(file);
try {
let out: RawMeta | RawPage | undefined;
switch (ext) {
case '.json':
out = await json(project, absolutePath, file);
break;
case '.mdx':
case '.md':
out = await md(project, absolutePath, file);
break;
}
if (out === undefined) filesCache.delete(absolutePath);
else filesCache.set(absolutePath, out);
return out;
} catch (e) {
console.error(`error when parsing ${file}`, e);
filesCache.delete(absolutePath);
}
}),
);
}
const projects = normalizeProjects(config.projects);
const all = (await Promise.all(projects.map(project))).flat();
return {
metas: all.filter((item) => item?.type === 'meta'),
pages: all.filter((item) => item?.type === 'page'),
};
}
+39
View File
@@ -0,0 +1,39 @@
import type { FumapressConfig } from '@/config';
import { watch } from 'chokidar';
import { normalizeProjects } from './config';
import matcher from 'picomatch';
import path from 'node:path';
import type { Stats } from 'node:fs';
import processPatterns, { getOptions } from '../tinyglobby/patterns';
export function startWatcher(config: FumapressConfig) {
const projects = normalizeProjects(config.content?.projects);
const ignored = projects.map((project) => {
const processed = processPatterns(getOptions({ cwd: project.dir }), project.include, {
root: project.dir,
depthOffset: 0,
});
const isMatch = matcher(processed.match, {
ignore: processed.ignore,
});
return (filePath: string, stats?: Stats): boolean => {
if (stats && stats.isFile()) {
return !isMatch(path.relative(project.dir, filePath));
}
return false;
};
});
const watcher = watch(
projects.map((project) => project.dir),
{
ignoreInitial: true,
followSymlinks: false,
ignored,
},
);
return watcher;
}
@@ -0,0 +1,154 @@
/* from https://github.com/SuperchupuDev/tinyglobby/blob/main/src/patterns.ts */
import { isAbsolute, posix } from 'node:path';
import picomatch from 'picomatch';
import { escapePath, type GlobOptions, isDynamicPattern } from 'tinyglobby';
const PARENT_DIRECTORY = /^(\/?\.\.)+/;
const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g;
export interface InternalProps {
root: string;
commonPath?: string[];
depthOffset: number;
}
export type InternalOptions = Pick<
Required<GlobOptions>,
| 'caseSensitiveMatch'
| 'cwd'
| 'debug'
| 'expandDirectories'
| 'followSymbolicLinks'
| 'onlyFiles'
| 'ignore'
> &
GlobOptions;
export interface ProcessedPatterns {
match: string[];
ignore: string[];
}
function normalizePattern(
pattern: string,
opts: InternalOptions,
props: InternalProps,
isIgnore: boolean,
) {
const cwd = opts.cwd as string;
let result: string = pattern;
if (pattern[pattern.length - 1] === '/') {
result = pattern.slice(0, -1);
}
// using a directory as entry should match all files inside it
if (result[result.length - 1] !== '*' && opts.expandDirectories) {
result += '/**';
}
const escapedCwd = escapePath(cwd);
result = isAbsolute(result.replace(ESCAPING_BACKSLASHES, ''))
? posix.relative(escapedCwd, result)
: posix.normalize(result);
const parentDir = PARENT_DIRECTORY.exec(result)?.[0];
const parts = splitPattern(result);
let i = 0;
if (parentDir) {
const n = (parentDir.length + 1) / 3;
// normalize a pattern like `../foo/bar` to `bar` when cwd ends with `/foo`
const cwdParts = escapedCwd.split('/');
while (i < n && parts[i + n] === cwdParts[cwdParts.length + i - n]) {
result = `${result.slice(0, (n - i - 1) * 3)}${result.slice((n - i) * 3 + parts[i++ + n]!.length + 1) || '.'}`;
}
// move root `n` directories up
const potentialRoot = posix.join(cwd, parentDir.slice(i * 3));
// windows can make the potential root something like `../C:`, we don't want that
if (potentialRoot[0] !== '.' && props.root.length > potentialRoot.length) {
props.root = potentialRoot;
props.depthOffset = -n + i;
}
}
if (!isIgnore && props.depthOffset >= 0) {
props.commonPath ??= parts;
const newCommonPath: string[] = [];
const length = Math.min(props.commonPath.length, parts.length);
for (i = 0; i < length; i++) {
const part = parts[i]!;
if (part === '**' && !parts[i + 1]) {
newCommonPath.pop();
break;
}
if (i === parts.length - 1 || part !== props.commonPath[i] || isDynamicPattern(part)) {
break;
}
}
props.depthOffset = newCommonPath.length;
props.commonPath = newCommonPath;
props.root = newCommonPath.length > 0 ? posix.join(cwd, ...newCommonPath) : cwd;
}
return result;
}
//#endregion normalizePattern
//#region processPatterns
export default function processPatterns(
options: InternalOptions,
patterns: readonly string[],
props: InternalProps,
): ProcessedPatterns {
const matchPatterns: string[] = [];
const ignorePatterns: string[] = [];
for (const pattern of options.ignore) {
// don't handle negated patterns here for consistency with fast-glob
if (pattern && (pattern[0] !== '!' || pattern[1] === '(')) {
ignorePatterns.push(normalizePattern(pattern, options, props, true));
}
}
for (const pattern of patterns) {
if (pattern) {
if (pattern[0] !== '!' || pattern[1] === '(') {
matchPatterns.push(normalizePattern(pattern, options, props, false));
} else if (pattern[1] !== '!' || pattern[2] === '(') {
ignorePatterns.push(normalizePattern(pattern.slice(1), options, props, true));
}
}
}
return { match: matchPatterns, ignore: ignorePatterns };
}
const splitPatternOptions = { parts: true };
export function splitPattern(path: string): string[] {
const result = picomatch.scan(path, splitPatternOptions);
return result.parts?.length ? result.parts : [path];
}
const defaultOptions: GlobOptions = {
caseSensitiveMatch: true,
cwd: process.cwd(),
debug: !!process.env.TINYGLOBBY_DEBUG,
expandDirectories: true,
followSymbolicLinks: true,
onlyFiles: true,
};
export function getOptions(options?: GlobOptions): InternalOptions {
const opts = { ...defaultOptions, ...options } as InternalOptions;
opts.ignore ??= [];
return opts;
}
+140
View File
@@ -0,0 +1,140 @@
import path from 'node:path';
import { serve } from '@hono/node-server';
import { serveStatic } from '@hono/node-server/serve-static';
import type { MiddlewareHandler } from 'hono';
import { Hono } from 'hono/tiny';
import { unstable_createServerEntryAdapter as createServerEntryAdapter } from 'waku/adapter-builders';
import {
unstable_constants as constants,
unstable_honoMiddleware as honoMiddleware,
} from 'waku/internals';
import type { BuildOptions } from 'waku/adapters/node-build-enhancer';
import { createNodeWebSocket } from '@hono/node-ws';
import { startWatcher } from '../source/watcher';
import { getConfigRuntime } from '@/config/load-runtime';
import { type WSContext } from 'hono/ws';
import { encodeEvent, WebSocketEvent } from './hot-reload';
import { filesCache } from '../source/storage';
import { getSource } from '../source';
const { DIST_PUBLIC } = constants;
const { contextMiddleware, rscMiddleware, middlewareRunner } = honoMiddleware;
export default createServerEntryAdapter(
(
{ processRequest, processBuild, config, isBuild, notFoundHtml },
options?: {
middlewareFns?: (() => MiddlewareHandler)[];
middlewareModules?: Record<string, () => Promise<unknown>>;
},
) => {
const { middlewareFns = [], middlewareModules = {} } = options || {};
const app = new Hono();
let serveFn = serve;
app.notFound((c) => {
if (notFoundHtml) {
return c.html(notFoundHtml, 404);
}
return c.text('404 Not Found', 404);
});
if (isBuild) {
app.use(
`${config.basePath}*`,
serveStatic({
root: path.join(config.distDir, DIST_PUBLIC),
rewriteRequestPath: (path) => path.slice(config.basePath.length - 1),
}),
);
}
if (isBuild && process.env.HOT_RELOAD === '1') {
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
const clients = new Set<WSContext>();
void initHotReload(clients).catch((e) => {
console.error(e);
process.exit(1);
});
app.get(
'/_ws',
upgradeWebSocket(() => ({
onOpen(_, ws) {
clients.add(ws);
},
onClose(_, ws) {
clients.delete(ws);
},
})),
);
serveFn = (...args) => {
const server = serve(...args);
injectWebSocket(server);
for (const signal of ['SIGTERM', 'SIGINT'])
process.once(signal, () => {
server.close();
});
return server;
};
}
app.use(contextMiddleware() as never);
for (const middlewareFn of middlewareFns) {
app.use(middlewareFn());
}
app.use(middlewareRunner(middlewareModules as never) as never);
app.use(rscMiddleware({ processRequest }) as never);
const buildOptions: BuildOptions = {
distDir: config.distDir,
};
return {
fetch: app.fetch,
build: processBuild,
buildOptions,
buildEnhancers: ['waku/adapters/node-build-enhancer'],
serve: serveFn,
};
},
);
async function initHotReload(clients: Set<WSContext>) {
const watcher = startWatcher(await getConfigRuntime());
function send(event: WebSocketEvent) {
const encoded = encodeEvent(event);
for (const client of clients) client.send(encoded);
}
watcher.on('all', (event, filePath) => {
if (event === 'change') {
filesCache.delete(filePath);
send({
type: 'clear-cache',
absolutePath: filePath,
});
}
switch (event) {
case 'add':
case 'change':
case 'unlink':
getSource.revalidate(false);
send({
type: 'revalidate',
});
break;
}
});
for (const signal of ['SIGTERM', 'SIGINT'])
process.once(signal, () => {
watcher.close();
});
}
+32
View File
@@ -0,0 +1,32 @@
import { z } from 'zod';
const OnClearCacheSchema = z.object({
type: z.literal('clear-cache'),
absolutePath: z.string(),
});
const OnRevalidateSchema = z.object({
type: z.literal('revalidate'),
});
export const WebSocketEventSchema = z.discriminatedUnion('type', [
OnClearCacheSchema,
OnRevalidateSchema,
]);
// TypeScript type inferred from the schema (equivalent to your original union)
export type WebSocketEvent = z.infer<typeof WebSocketEventSchema>;
export type OnClearCache = z.infer<typeof OnClearCacheSchema>;
export type OnRevalidate = z.infer<typeof OnRevalidateSchema>;
export function encodeEvent(event: WebSocketEvent) {
return JSON.stringify(event);
}
export function decodeEvent(event: string): WebSocketEvent | undefined {
try {
return WebSocketEventSchema.safeParse(JSON.parse(event)).data;
} catch {
return;
}
}
+2
View File
@@ -1,6 +1,7 @@
import type { ReactNode } from 'react';
import { Provider } from '@/components/provider';
import '@/styles/globals.css';
import { HotReload } from '@/components/hot-reload';
export default async function RootElement({ children }: { children: ReactNode }) {
return (
@@ -8,6 +9,7 @@ export default async function RootElement({ children }: { children: ReactNode })
<head></head>
<body data-version="1.0" className="flex flex-col min-h-screen">
<Provider>{children}</Provider>
{process.env.HOT_RELOAD === '1' && <HotReload />}
</body>
</html>
);
+8
View File
@@ -2,7 +2,15 @@ declare module 'process' {
global {
namespace NodeJS {
interface ProcessEnv {
/**
* the project directory (where fumapress is started)
*/
PROJECT_DIR?: string;
/**
* whether hot reload is enabled (e.g. local file watcher)
*/
HOT_RELOAD?: '1';
}
}
}
+3 -2
View File
@@ -5,13 +5,14 @@ import type { UserConfig } from 'vite';
export default defineConfig({
distDir: 'dist/waku',
unstable_adapter: './src/lib/waku/adapter.ts',
vite: {
// we do this to avoid Vite from bundling React contexts and cause duplicated contexts conflicts.
optimizeDeps: {
exclude: ['fumadocs-ui', 'fumadocs-core'],
},
ssr: {
external: ['@takumi-rs/image-response', 'unrun'],
resolve: {
external: ['@takumi-rs/image-response', 'unrun', 'chokidar'],
},
plugins: [tailwindcss(), tsconfigPaths()],
+57 -17
View File
@@ -2453,9 +2453,18 @@ importers:
packages/press:
dependencies:
'@hono/node-server':
specifier: ^1.19.10
version: 1.19.10([email protected])
'@hono/node-ws':
specifier: ^1.3.0
version: 1.3.0(@hono/[email protected]([email protected]))([email protected])
'@takumi-rs/image-response':
specifier: ^0.69.5
version: 0.69.5
chokidar:
specifier: ^5.0.0
version: 5.0.0
commander:
specifier: ^14.0.3
version: 14.0.3
@@ -2474,9 +2483,15 @@ importers:
hast-util-to-jsx-runtime:
specifier: ^2.3.6
version: 2.3.6
hono:
specifier: ^4.12.4
version: 4.12.4
lucide-react:
specifier: ^0.575.0
version: 0.575.0([email protected])
picomatch:
specifier: ^4.0.3
version: 4.0.3
react:
specifier: ^19.2.4
version: 19.2.4
@@ -2507,9 +2522,6 @@ importers:
vfile:
specifier: ^6.0.3
version: 6.0.3
vite:
specifier: ^7.3.1
version: 7.3.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
waku:
specifier: 1.0.0-alpha.5
version: 1.0.0-alpha.5(@types/[email protected])([email protected])([email protected])([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected])([email protected])([email protected])
@@ -2532,6 +2544,9 @@ importers:
'@types/node':
specifier: ^25.3.3
version: 25.3.3
'@types/picomatch':
specifier: ^4.0.2
version: 4.0.2
'@types/react':
specifier: ^19.2.14
version: 19.2.14
@@ -2556,6 +2571,9 @@ importers:
unified:
specifier: ^11.0.5
version: 11.0.5
vite:
specifier: ^7.3.1
version: 7.3.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
vite-tsconfig-paths:
specifier: ^6.1.1
version: 6.1.1([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
@@ -3942,12 +3960,25 @@ packages:
peerDependencies:
vue: ^3.2.0
'@hono/[email protected]':
resolution: {integrity: sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
'@hono/[email protected]':
resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
'@hono/[email protected]':
resolution: {integrity: sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q==}
engines: {node: '>=18.14.1'}
peerDependencies:
'@hono/node-server': ^1.19.2
hono: ^4.6.0
'@humanfs/[email protected]':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -9075,14 +9106,14 @@ packages:
[email protected]:
resolution: {integrity: sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A==}
[email protected]:
resolution: {integrity: sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==}
engines: {node: '>=16.9.0'}
[email protected]:
resolution: {integrity: sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg==}
engines: {node: '>=16.9.0'}
[email protected]:
resolution: {integrity: sha512-ooiZW1Xy8rQ4oELQ++otI2T9DsKpV0M6c6cO6JGx4RTfav9poFFLlet9UMXHZnoM1yG0HWGlQLswBGX3RZmHtg==}
engines: {node: '>=16.9.0'}
[email protected]:
resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==}
@@ -13527,14 +13558,23 @@ snapshots:
'@tanstack/vue-virtual': 3.13.19([email protected]([email protected]))
vue: 3.5.29([email protected])
'@hono/[email protected].9([email protected].2)':
'@hono/[email protected].10([email protected].4)':
dependencies:
hono: 4.12.2
hono: 4.12.4
'@hono/[email protected]([email protected])':
dependencies:
hono: 4.12.3
'@hono/[email protected](@hono/[email protected]([email protected]))([email protected])':
dependencies:
'@hono/node-server': 1.19.10([email protected])
hono: 4.12.4
ws: 8.19.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@humanfs/[email protected]': {}
'@humanfs/[email protected]':
@@ -13832,7 +13872,7 @@ snapshots:
'@modelcontextprotocol/[email protected]([email protected])':
dependencies:
'@hono/node-server': 1.19.9([email protected].2)
'@hono/node-server': 1.19.9([email protected].3)
ajv: 8.18.0
ajv-formats: 3.0.1([email protected])
content-type: 1.0.5
@@ -13842,7 +13882,7 @@ snapshots:
eventsource-parser: 3.0.6
express: 5.2.1
express-rate-limit: 8.2.1([email protected])
hono: 4.12.2
hono: 4.12.3
jose: 6.1.3
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
@@ -13854,7 +13894,7 @@ snapshots:
'@modelcontextprotocol/[email protected]([email protected])':
dependencies:
'@hono/node-server': 1.19.9([email protected].2)
'@hono/node-server': 1.19.9([email protected].3)
ajv: 8.18.0
ajv-formats: 3.0.1([email protected])
content-type: 1.0.5
@@ -13864,7 +13904,7 @@ snapshots:
eventsource-parser: 3.0.6
express: 5.2.1
express-rate-limit: 8.2.1([email protected])
hono: 4.12.2
hono: 4.12.3
jose: 6.1.3
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
@@ -19228,10 +19268,10 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
@@ -22925,11 +22965,11 @@ snapshots:
[email protected](@types/[email protected])([email protected])([email protected])([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected])([email protected])([email protected]):
dependencies:
'@hono/node-server': 1.19.9([email protected].3)
'@hono/node-server': 1.19.10([email protected].4)
'@vitejs/plugin-react': 5.1.4([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
'@vitejs/plugin-rsc': 0.5.21([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
dotenv: 17.3.1
hono: 4.12.3
hono: 4.12.4
magic-string: 0.30.21
picocolors: 1.1.1
react: 19.2.4