release: WRNexusJS 0.8.0
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
* the running process while the HMR socket morphs fresh HTML into the browser.
|
||||
*/
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { resolve, dirname, isAbsolute, join } from "node:path";
|
||||
import type { Middleware, Mode, SecurityConfig, SeoConfig } from "@wrnexus/core";
|
||||
import { buildRouter, type Router } from "@wrnexus/router";
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
setDb,
|
||||
registerDb,
|
||||
registerLazyDb,
|
||||
getDbPerformanceSnapshot,
|
||||
} from "@wrnexus/db";
|
||||
import { connectFromConfig } from "@wrnexus/db/connect";
|
||||
import { configureStorage, type StorageConfig } from "@wrnexus/uploader";
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
loadWrnServerModule,
|
||||
setCompileCacheDir,
|
||||
setCompileImportOptions,
|
||||
setDevCompilerPipeline,
|
||||
wrnBrowserArtifactUrl,
|
||||
} from "./pipeline.ts";
|
||||
import { createRpcHandler } from "@wrnexus/ssr/rpc";
|
||||
@@ -48,18 +50,27 @@ import { resolvePackageMigrations } from "./plugin-migrations.ts";
|
||||
import { HmrHub } from "./hmr.ts";
|
||||
import { startWatcher } from "./watch.ts";
|
||||
export { RESTART_EXIT_CODE } from "./restart.ts";
|
||||
export { expandStaticComponents, precomputePartialStaticShell } from "./partial-build.ts";
|
||||
export { getWrnCompileMetrics, resetWrnCompileMetrics } from "./pipeline.ts";
|
||||
export type { WrnCompileMetrics } from "./pipeline.ts";
|
||||
import { resetDevCache } from "./cache.ts";
|
||||
|
||||
import type { DevToolbarConfig } from "@wrnexus/dev-toolbar/types";
|
||||
import { createPluginRunner, discoverPlugins, type PluginInput } from "@wrnexus/plugin";
|
||||
import type { ObservabilityConfig, TenancyConfig } from "@wrnexus/styles";
|
||||
|
||||
import { createDevToolbarCollector, type DevToolbarCollector } from "@wrnexus/dev-toolbar/server";
|
||||
import {
|
||||
builtinDevToolbarPanels,
|
||||
createDevToolbarCollector,
|
||||
type DevToolbarCollector,
|
||||
} from "@wrnexus/dev-toolbar/server";
|
||||
|
||||
export interface ServeOptions {
|
||||
appDir: string;
|
||||
port?: number;
|
||||
hostname?: string;
|
||||
/** Development TLS material. Production TLS is normally terminated by the deployment proxy. */
|
||||
tls?: { cert: string; key: string };
|
||||
mode?: Mode;
|
||||
/** Inject the live-reload client (defaults to true in development). */
|
||||
hmr?: boolean;
|
||||
@@ -193,6 +204,12 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
includeDevDependencies: true,
|
||||
strict: true,
|
||||
warn: (message) => console.warn(`[wrnexus:plugin] ${message}`),
|
||||
enforcePermissions: (opts.appConfig?.pluginPermissions as { enforce?: boolean } | undefined)
|
||||
?.enforce,
|
||||
grantedPermissions: (
|
||||
opts.appConfig?.pluginPermissions as
|
||||
{ grants?: Record<string, import("@wrnexus/plugin").PluginPermission[]> } | undefined
|
||||
)?.grants,
|
||||
});
|
||||
|
||||
const pluginRunner = createPluginRunner(discoveredPlugins, {
|
||||
@@ -252,6 +269,30 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
);
|
||||
|
||||
const pluginContributions = await pluginRunner.contributions();
|
||||
const virtualModules = new Map<string, string>();
|
||||
const virtualDir = join(appRoot, ".wrnexus", "virtual");
|
||||
mkdirSync(virtualDir, { recursive: true });
|
||||
for (const [index, module] of pluginContributions.virtualModules.entries()) {
|
||||
const output = join(virtualDir, `plugin-${index}.ts`);
|
||||
writeFileSync(
|
||||
output,
|
||||
await module.load({
|
||||
root: appRoot,
|
||||
mode,
|
||||
command: "dev",
|
||||
profile: process.env.WRNEXUS_PROFILE,
|
||||
metadata: new Map(),
|
||||
warn: (message) => console.warn(`[wrnexus:plugin] ${message}`),
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
virtualModules.set(module.id, output);
|
||||
}
|
||||
setDevCompilerPipeline({
|
||||
transformAst: (ast, file) => pluginRunner.transformAst(ast, file),
|
||||
transformCode: (code, file) => pluginRunner.transformCode(code, file),
|
||||
virtualModules,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[wrnexus:plugin] discovered: ${
|
||||
@@ -296,7 +337,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
const schemasJs = await schemaRuntime(router);
|
||||
|
||||
// i18n is opt-in by the presence of app/locales/*.json.
|
||||
const localeMessages = loadLocales(join(appDir, "locales"));
|
||||
const localeMessages = loadLocales(join(appDir, "locales"), { strict: opts.i18n?.strict });
|
||||
const i18n = Object.keys(localeMessages).length
|
||||
? resolveI18n(localeMessages, opts.i18n)
|
||||
: undefined;
|
||||
@@ -369,6 +410,34 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
})
|
||||
: undefined;
|
||||
const middleware = middlewareLoader(router);
|
||||
const toolbarPlatform = {
|
||||
wrnexus060: {
|
||||
clientFunctions: true,
|
||||
serverFunctions: true,
|
||||
sharedFunctions: true,
|
||||
typedOutputs: true,
|
||||
requestScopedStores: true,
|
||||
},
|
||||
plugins: pluginRunner.plugins.map((plugin) => ({ name: plugin.name, version: plugin.version })),
|
||||
runtimes: pluginContributions.clientRuntimes.map((runtime) => ({
|
||||
id: runtime.id,
|
||||
publicPath: runtime.publicPath,
|
||||
type: runtime.type,
|
||||
load: runtime.load,
|
||||
})),
|
||||
assets: pluginContributions.assets.map((asset) => ({
|
||||
id: asset.id,
|
||||
publicPath: asset.publicPath,
|
||||
contentType: asset.contentType,
|
||||
})),
|
||||
componentDirs,
|
||||
styles: pluginContributions.styles,
|
||||
routes: {
|
||||
pages: router.pages.length,
|
||||
api: router.api.length,
|
||||
realtime: router.realtime.length,
|
||||
},
|
||||
};
|
||||
|
||||
const runtimeDeps = {
|
||||
mode,
|
||||
@@ -392,13 +461,14 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
clientRuntimes: pluginContributions.clientRuntimes,
|
||||
hub,
|
||||
realtimeBus: realtimeBusFromConfig(opts.realtime),
|
||||
renderHtml: (html: string) => pluginRunner.render(html),
|
||||
devToolbar:
|
||||
devToolbarConfig && devToolbarCollector
|
||||
? {
|
||||
config: devToolbarConfig,
|
||||
collector: devToolbarCollector,
|
||||
root: appRoot,
|
||||
panels: [
|
||||
panels: () => [
|
||||
{
|
||||
id: "runtime",
|
||||
title: "Runtime",
|
||||
@@ -415,39 +485,23 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
"Global/page stores, safe client state, computed values, actions, persistence, and hydration.",
|
||||
order: 20,
|
||||
},
|
||||
{
|
||||
id: "cache",
|
||||
title: "Cache",
|
||||
icon: "layers",
|
||||
description:
|
||||
"Request, data, component, and page cache entries plus hit/miss history.",
|
||||
order: 30,
|
||||
},
|
||||
...builtinDevToolbarPanels({
|
||||
root: appRoot,
|
||||
platform: toolbarPlatform,
|
||||
database: getDbPerformanceSnapshot(),
|
||||
version: { current: "0.8.0" },
|
||||
}),
|
||||
...pluginToolbarPanels,
|
||||
],
|
||||
platform: {
|
||||
wrnexus060: {
|
||||
clientFunctions: true,
|
||||
serverFunctions: true,
|
||||
sharedFunctions: true,
|
||||
typedOutputs: true,
|
||||
requestScopedStores: true,
|
||||
},
|
||||
plugins: pluginRunner.plugins.map((plugin) => ({
|
||||
name: plugin.name,
|
||||
version: plugin.version,
|
||||
})),
|
||||
runtimes: pluginContributions.clientRuntimes.map((runtime) => ({
|
||||
id: runtime.id,
|
||||
publicPath: runtime.publicPath,
|
||||
type: runtime.type,
|
||||
load: runtime.load,
|
||||
})),
|
||||
assets: pluginContributions.assets.map((asset) => ({
|
||||
id: asset.id,
|
||||
publicPath: asset.publicPath,
|
||||
contentType: asset.contentType,
|
||||
})),
|
||||
componentDirs,
|
||||
styles: pluginContributions.styles,
|
||||
routes: {
|
||||
pages: router.pages.length,
|
||||
api: router.api.length,
|
||||
realtime: router.realtime.length,
|
||||
},
|
||||
},
|
||||
platform: toolbarPlatform,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
@@ -501,6 +555,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
hostname,
|
||||
development: mode === "development",
|
||||
maxRequestBodySize: 10 * 1024 * 1024,
|
||||
...(opts.tls ? { tls: opts.tls } : {}),
|
||||
fetch(request, server) {
|
||||
if (new URL(request.url).pathname === "/__wrnexus/rpc") return rpcHandler(request);
|
||||
return handlers.fetch(request, server);
|
||||
@@ -551,13 +606,21 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
assets.updateSchemas(await schemaRuntime(router));
|
||||
}
|
||||
if (appFiles.some((file) => file === "locales" || file.startsWith("locales/"))) {
|
||||
const messages = loadLocales(join(appDir, "locales"));
|
||||
runtimeDeps.i18n = Object.keys(messages).length
|
||||
? resolveI18n(messages, opts.i18n)
|
||||
: undefined;
|
||||
try {
|
||||
const messages = loadLocales(join(appDir, "locales"), { strict: opts.i18n?.strict });
|
||||
runtimeDeps.i18n = Object.keys(messages).length
|
||||
? resolveI18n(messages, opts.i18n)
|
||||
: undefined;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"[wrnexus] locale hot update was incomplete; keeping the last valid bundle",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[wrnexus] hot update — ${files.join(", ")}`);
|
||||
await pluginRunner.hook("hmrUpdate", files);
|
||||
const storeUpdates: Array<{ name: string; url: string; kind: string }> = [];
|
||||
for (const changed of files) {
|
||||
const absolute = isAbsolute(changed) ? changed : resolve(appDir, changed);
|
||||
@@ -634,6 +697,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
watcher?.close();
|
||||
unsubscribeDevToolbar?.();
|
||||
server.stop();
|
||||
void pluginRunner.hook("shutdown");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user