release: WRNexusJS 0.8.0
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { basename, extname, join, relative } from "node:path";
|
||||
import type { DevToolbarPanel, DevToolbarPlatformSnapshot } from "../types.ts";
|
||||
|
||||
function walk(root: string, test: (file: string) => boolean): string[] {
|
||||
if (!existsSync(root)) return [];
|
||||
const result: string[] = [];
|
||||
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
||||
const file = join(root, entry.name);
|
||||
if (entry.isDirectory()) result.push(...walk(file, test));
|
||||
else if (test(file)) result.push(file);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function flatten(value: unknown, prefix = ""): string[] {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return prefix ? [prefix] : [];
|
||||
return Object.entries(value).flatMap(([key, child]) =>
|
||||
flatten(child, prefix ? `${prefix}.${key}` : key),
|
||||
);
|
||||
}
|
||||
|
||||
export interface BuiltinPanelOptions {
|
||||
root: string;
|
||||
platform?: DevToolbarPlatformSnapshot;
|
||||
database?: { queries: unknown[]; issues: unknown[] };
|
||||
version?: { current: string; latest?: string };
|
||||
}
|
||||
|
||||
/** Produce bounded, credential-free first-party inspector data. */
|
||||
export function builtinDevToolbarPanels(options: BuiltinPanelOptions): DevToolbarPanel[] {
|
||||
const app = join(options.root, "app");
|
||||
const queueFiles = walk(join(app, "queues"), (file) => /\.[cm]?[jt]s$/.test(file));
|
||||
const localeFiles = walk(join(app, "locales"), (file) => extname(file) === ".json");
|
||||
const locales = localeFiles.map((file) => {
|
||||
try {
|
||||
return {
|
||||
name: basename(file, ".json"),
|
||||
keys: flatten(JSON.parse(readFileSync(file, "utf8"))),
|
||||
};
|
||||
} catch {
|
||||
return { name: basename(file, ".json"), keys: [] as string[], invalid: true };
|
||||
}
|
||||
});
|
||||
const allKeys = new Set(locales.flatMap((locale) => locale.keys));
|
||||
const translations = locales.map((locale) => ({
|
||||
...locale,
|
||||
missing: [...allKeys].filter((key) => !locale.keys.includes(key)).slice(0, 100),
|
||||
}));
|
||||
const styleFiles = walk(app, (file) => /\.(?:css|wrn)$/.test(file));
|
||||
const cssBytes = styleFiles.reduce((sum, file) => sum + statSync(file).size, 0);
|
||||
const memory = typeof process.memoryUsage === "function" ? process.memoryUsage() : undefined;
|
||||
const database = options.database ?? { queries: [], issues: [] };
|
||||
const realtimeMonitor = (globalThis as typeof globalThis & { __wrnexusRealtimeMonitor?: unknown })
|
||||
.__wrnexusRealtimeMonitor;
|
||||
const latest = options.version?.latest;
|
||||
return [
|
||||
{
|
||||
id: "database",
|
||||
title: "SQL",
|
||||
category: "database",
|
||||
badge: database.issues.length,
|
||||
data: { recent: database.queries.slice(-100), issues: database.issues.slice(-100) },
|
||||
},
|
||||
{
|
||||
id: "queues",
|
||||
title: "Queues",
|
||||
category: "queues",
|
||||
badge: queueFiles.length,
|
||||
data: {
|
||||
definitions: queueFiles.map((file) => relative(options.root, file).replace(/\\/g, "/")),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "realtime",
|
||||
title: "Realtime",
|
||||
category: "realtime",
|
||||
badge: options.platform?.routes?.realtime ?? 0,
|
||||
data: {
|
||||
routes: options.platform?.routes?.realtime ?? 0,
|
||||
monitor: realtimeMonitor ?? { rooms: 0, messages: 0, acknowledgements: 0 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "translations",
|
||||
title: "Translations",
|
||||
category: "translations",
|
||||
badge: translations.reduce((sum, locale) => sum + locale.missing.length, 0),
|
||||
data: { locales: translations },
|
||||
},
|
||||
{
|
||||
id: "css",
|
||||
title: "CSS",
|
||||
category: "css",
|
||||
badge: styleFiles.length,
|
||||
data: { files: styleFiles.length, bytes: cssBytes, browserUnusedSelectorScan: true },
|
||||
},
|
||||
{
|
||||
id: "performance",
|
||||
title: "Memory & Vitals",
|
||||
category: "performance",
|
||||
data: { memory, webVitalsEndpoint: "/__wrnexus/metrics/vitals" },
|
||||
},
|
||||
{
|
||||
id: "upgrades",
|
||||
title: "Upgrades",
|
||||
category: "upgrades",
|
||||
badge: latest && latest !== options.version?.current ? 1 : 0,
|
||||
data: { current: options.version?.current, latest, updateCommand: "wrnexus update --latest" },
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export * from "./serialize.ts";
|
||||
export * from "./issues.ts";
|
||||
export * from "./editor.ts";
|
||||
export * from "./routes.ts";
|
||||
export * from "./builtin-panels.ts";
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface DevToolbarRouteOptions {
|
||||
editor?: string;
|
||||
allowOpenEditor?: boolean;
|
||||
platform?: DevToolbarPlatformSnapshot;
|
||||
panels?: DevToolbarPanel[];
|
||||
panels?: DevToolbarPanel[] | (() => DevToolbarPanel[] | Promise<DevToolbarPanel[]>);
|
||||
}
|
||||
|
||||
const json = (value: unknown, status = 200) =>
|
||||
@@ -39,8 +39,10 @@ export async function handleDevToolbarRoute(
|
||||
return json({
|
||||
issues: options.collector.getIssues(url.searchParams.get("pathname") ?? undefined),
|
||||
});
|
||||
if (url.pathname === "/__wrnexus/dev-toolbar/platform" && request.method === "GET")
|
||||
return json({ platform: options.platform ?? null, panels: options.panels ?? [] });
|
||||
if (url.pathname === "/__wrnexus/dev-toolbar/platform" && request.method === "GET") {
|
||||
const panels = typeof options.panels === "function" ? await options.panels() : options.panels;
|
||||
return json({ platform: options.platform ?? null, panels: panels ?? [] });
|
||||
}
|
||||
if (url.pathname === "/__wrnexus/dev-toolbar/open-editor" && request.method === "POST") {
|
||||
if (options.allowOpenEditor === false)
|
||||
return json({ error: "Open in editor is disabled." }, 403);
|
||||
|
||||
Reference in New Issue
Block a user