mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(cli): add single executable with embedded web assets support
Implement support for bundling web assets into CLI single executable binaries. When built with --with-web-assets, the executable includes the compiled web application and serves it directly without file system access. A stub generator creates empty manifests for normal builds to maintain compatibility. Key changes: - Add --with-web-assets flag to build-executable.ts with manifest validation - Generate embeddedAssets.ts manifest from web/dist during build - Serve embedded assets in web server with fallback to file system - Add hapi server subcommand to start API + web server - Include server sources in CLI tsconfig for compilation scope - Add workspace-level build:single-exe scripts for production builds
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
**/dist/
|
**/dist/
|
||||||
**/dist-exe/
|
**/dist-exe/
|
||||||
**/*.tsbuildinfo
|
**/*.tsbuildinfo
|
||||||
|
server/src/web/embeddedAssets.generated.ts
|
||||||
|
|
||||||
# Env files (can contain secrets)
|
# Env files (can contain secrets)
|
||||||
**/.env
|
**/.env
|
||||||
|
|||||||
@@ -70,6 +70,8 @@
|
|||||||
"build": "shx rm -rf dist && tsc --noEmit && pkgroll",
|
"build": "shx rm -rf dist && tsc --noEmit && pkgroll",
|
||||||
"build:exe": "bun run scripts/build-executable.ts",
|
"build:exe": "bun run scripts/build-executable.ts",
|
||||||
"build:exe:all": "bun run scripts/build-executable.ts --all",
|
"build:exe:all": "bun run scripts/build-executable.ts --all",
|
||||||
|
"build:exe:allinone": "bun run scripts/build-executable.ts --with-web-assets",
|
||||||
|
"build:exe:allinone:all": "bun run scripts/build-executable.ts --with-web-assets --all",
|
||||||
"test": "bun run build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
"test": "bun run build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
||||||
"test:win": "bun run build && vitest run",
|
"test:win": "bun run build && vitest run",
|
||||||
"start": "bun run build && ./bin/happy.mjs",
|
"start": "bun run build && ./bin/happy.mjs",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { dirname, isAbsolute, join } from 'node:path';
|
import { dirname, isAbsolute, join } from 'node:path';
|
||||||
import { existsSync, mkdirSync } from 'node:fs';
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
const DEFAULT_TARGETS = [
|
const DEFAULT_TARGETS = [
|
||||||
@@ -129,6 +129,52 @@ function resolveOutdir(projectRoot: string, outdir: string): string {
|
|||||||
return join(projectRoot, outdir);
|
return join(projectRoot, outdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function writeStubEmbeddedAssets(workspaceRoot: string): void {
|
||||||
|
const outputPath = join(workspaceRoot, 'server', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||||
|
const contents = [
|
||||||
|
'// This file is generated by cli/scripts/build-executable.ts when --with-web-assets is not used.',
|
||||||
|
'// It intentionally contains no embedded assets.',
|
||||||
|
'',
|
||||||
|
'export interface EmbeddedWebAsset {',
|
||||||
|
' path: string;',
|
||||||
|
' sourcePath: string;',
|
||||||
|
' mimeType: string;',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
'export const embeddedAssets: EmbeddedWebAsset[] = [];',
|
||||||
|
''
|
||||||
|
].join('\n');
|
||||||
|
writeFileSync(outputPath, contents, 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStubEmbeddedAssets(manifestPath: string): boolean {
|
||||||
|
try {
|
||||||
|
const contents = readFileSync(manifestPath, 'utf-8');
|
||||||
|
return contents.includes('intentionally contains no embedded assets');
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureEmbeddedAssetsManifest(workspaceRoot: string, includeWebAssets: boolean): void {
|
||||||
|
const manifestPath = join(workspaceRoot, 'server', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||||
|
if (includeWebAssets) {
|
||||||
|
if (!existsSync(manifestPath)) {
|
||||||
|
throw new Error(
|
||||||
|
'Missing embedded web asset manifest. Run `bun run build:web` and `cd server && bun run generate:embedded-web-assets`, or `bun run build:single-exe` from the repo root.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isStubEmbeddedAssets(manifestPath)) {
|
||||||
|
throw new Error(
|
||||||
|
'Embedded web asset manifest is a stub. Run `bun run build:web` and `cd server && bun run generate:embedded-web-assets`, or `bun run build:single-exe` from the repo root.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeStubEmbeddedAssets(workspaceRoot);
|
||||||
|
}
|
||||||
|
|
||||||
async function buildTarget(projectRoot: string, target: string, outdir: string, name: string): Promise<void> {
|
async function buildTarget(projectRoot: string, target: string, outdir: string, name: string): Promise<void> {
|
||||||
const { platform, arch } = parseTarget(target);
|
const { platform, arch } = parseTarget(target);
|
||||||
assertArchivesExist(projectRoot, platform, arch);
|
assertArchivesExist(projectRoot, platform, arch);
|
||||||
@@ -169,10 +215,11 @@ async function main(): Promise<void> {
|
|||||||
const outdirArg = getArg(args, '--outdir') ?? 'dist-exe';
|
const outdirArg = getArg(args, '--outdir') ?? 'dist-exe';
|
||||||
const name = getArg(args, '--name') ?? 'hapi';
|
const name = getArg(args, '--name') ?? 'hapi';
|
||||||
const buildAll = args.includes('--all');
|
const buildAll = args.includes('--all');
|
||||||
|
const includeWebAssets = args.includes('--with-web-assets');
|
||||||
|
|
||||||
if (args.includes('--target') && !target) {
|
if (args.includes('--target') && !target) {
|
||||||
console.error('Usage: bun run scripts/build-executable.ts [--target <bun-platform[-arch]>] [--outdir dist-exe] [--name hapi]');
|
console.error('Usage: bun run scripts/build-executable.ts [--target <bun-platform[-arch]>] [--outdir dist-exe] [--name hapi] [--with-web-assets]');
|
||||||
console.error(' or: bun run scripts/build-executable.ts --all [--outdir dist-exe] [--name hapi]');
|
console.error(' or: bun run scripts/build-executable.ts --all [--outdir dist-exe] [--name hapi] [--with-web-assets]');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,10 +229,13 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||||
const projectRoot = join(scriptDir, '..');
|
const projectRoot = join(scriptDir, '..');
|
||||||
|
const workspaceRoot = join(projectRoot, '..');
|
||||||
const outdir = resolveOutdir(projectRoot, outdirArg);
|
const outdir = resolveOutdir(projectRoot, outdirArg);
|
||||||
const resolvedTarget = buildAll ? undefined : resolveTarget(target);
|
const resolvedTarget = buildAll ? undefined : resolveTarget(target);
|
||||||
const targets = buildAll ? DEFAULT_TARGETS : [resolvedTarget!];
|
const targets = buildAll ? DEFAULT_TARGETS : [resolvedTarget!];
|
||||||
|
|
||||||
|
ensureEmbeddedAssetsManifest(workspaceRoot, includeWebAssets);
|
||||||
|
|
||||||
for (const targetName of targets) {
|
for (const targetName of targets) {
|
||||||
await buildTarget(projectRoot, targetName, outdir, name);
|
await buildTarget(projectRoot, targetName, outdir, name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ import { withBunRuntimeEnv } from './utils/bunRuntime'
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subcommand === 'server') {
|
||||||
|
try {
|
||||||
|
await import('../../server/src/index')
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error')
|
||||||
|
if (process.env.DEBUG) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await ensureRuntimeAssets()
|
await ensureRuntimeAssets()
|
||||||
|
|
||||||
// If --version is passed - do not log, its likely daemon inquiring about our version
|
// If --version is passed - do not log, its likely daemon inquiring about our version
|
||||||
@@ -323,6 +336,7 @@ ${chalk.bold('Usage:')}
|
|||||||
hapi mcp Start MCP stdio bridge
|
hapi mcp Start MCP stdio bridge
|
||||||
hapi connect (not available in direct-connect mode)
|
hapi connect (not available in direct-connect mode)
|
||||||
hapi notify (not available in direct-connect mode)
|
hapi notify (not available in direct-connect mode)
|
||||||
|
hapi server Start the API + web server
|
||||||
hapi daemon Manage background service that allows
|
hapi daemon Manage background service that allows
|
||||||
to spawn new sessions away from your computer
|
to spawn new sessions away from your computer
|
||||||
hapi doctor System diagnostics & troubleshooting
|
hapi doctor System diagnostics & troubleshooting
|
||||||
|
|||||||
+7
-4
@@ -6,12 +6,13 @@
|
|||||||
"es2022"
|
"es2022"
|
||||||
],
|
],
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"rootDir": "src",
|
"rootDir": "..",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"typeRoots": [
|
"types": [
|
||||||
"./node_modules/@types"
|
"node",
|
||||||
|
"bun-types"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
@@ -25,6 +26,8 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
"src/**/*.d.ts"
|
"src/**/*.d.ts",
|
||||||
|
"../server/src/**/*.ts",
|
||||||
|
"../server/src/**/*.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
"build:cli": "cd cli && bun run build",
|
"build:cli": "cd cli && bun run build",
|
||||||
"build:cli:exe": "cd cli && bun run build:exe",
|
"build:cli:exe": "cd cli && bun run build:exe",
|
||||||
"build:cli:exe:all": "cd cli && bun run build:exe:all",
|
"build:cli:exe:all": "cd cli && bun run build:exe:all",
|
||||||
|
"build:single-exe": "bun run build:web && (cd server && bun run generate:embedded-web-assets) && (cd cli && bun run build:exe:allinone)",
|
||||||
|
"build:single-exe:all": "bun run build:web && (cd server && bun run generate:embedded-web-assets) && (cd cli && bun run build:exe:allinone:all)",
|
||||||
"build:server": "cd server && bun run build",
|
"build:server": "cd server && bun run build",
|
||||||
"build:web": "cd web && bun run build",
|
"build:web": "cd web && bun run build",
|
||||||
"dev:server": "cd server && bun run dev",
|
"dev:server": "cd server && bun run dev",
|
||||||
|
|||||||
+2
-1
@@ -10,7 +10,8 @@
|
|||||||
"start": "bun run src/index.ts",
|
"start": "bun run src/index.ts",
|
||||||
"dev": "bun --watch run src/index.ts",
|
"dev": "bun --watch run src/index.ts",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"build": "bun build src/index.ts --outdir dist --target bun"
|
"build": "bun build src/index.ts --outdir dist --target bun",
|
||||||
|
"generate:embedded-web-assets": "bun run scripts/generate-embedded-web-assets.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@socket.io/bun-engine": "^0.1.0",
|
"@socket.io/bun-engine": "^0.1.0",
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { dirname, extname, join, relative, sep } from 'node:path';
|
||||||
|
import { existsSync, readdirSync, writeFileSync } from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const MIME_TYPES: Record<string, string> = {
|
||||||
|
'.css': 'text/css; charset=utf-8',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.html': 'text/html; charset=utf-8',
|
||||||
|
'.ico': 'image/x-icon',
|
||||||
|
'.jpeg': 'image/jpeg',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.js': 'text/javascript; charset=utf-8',
|
||||||
|
'.json': 'application/json; charset=utf-8',
|
||||||
|
'.map': 'application/json; charset=utf-8',
|
||||||
|
'.mjs': 'text/javascript; charset=utf-8',
|
||||||
|
'.otf': 'font/otf',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.ttf': 'font/ttf',
|
||||||
|
'.txt': 'text/plain; charset=utf-8',
|
||||||
|
'.wasm': 'application/wasm',
|
||||||
|
'.webmanifest': 'application/manifest+json; charset=utf-8',
|
||||||
|
'.webp': 'image/webp',
|
||||||
|
'.woff': 'font/woff',
|
||||||
|
'.woff2': 'font/woff2',
|
||||||
|
'.xml': 'application/xml; charset=utf-8'
|
||||||
|
};
|
||||||
|
|
||||||
|
function toPosixPath(filePath: string): string {
|
||||||
|
return filePath.split(sep).join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function listFiles(rootDir: string, currentDir = rootDir): string[] {
|
||||||
|
const entries = readdirSync(currentDir, { withFileTypes: true });
|
||||||
|
const files: string[] = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryPath = join(currentDir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
files.push(...listFiles(rootDir, entryPath));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry.isFile()) {
|
||||||
|
files.push(entryPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveMimeType(filePath: string): string {
|
||||||
|
const ext = extname(filePath).toLowerCase();
|
||||||
|
return MIME_TYPES[ext] ?? 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
function main(): void {
|
||||||
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const workspaceRoot = join(scriptDir, '..', '..');
|
||||||
|
const webDistDir = join(workspaceRoot, 'web', 'dist');
|
||||||
|
const outputPath = join(workspaceRoot, 'server', 'src', 'web', 'embeddedAssets.generated.ts');
|
||||||
|
const outputDir = dirname(outputPath);
|
||||||
|
|
||||||
|
if (!existsSync(webDistDir)) {
|
||||||
|
throw new Error(`Missing web/dist directory: ${webDistDir}. Run bun run build:web first.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexHtmlPath = join(webDistDir, 'index.html');
|
||||||
|
if (!existsSync(indexHtmlPath)) {
|
||||||
|
throw new Error(`Missing web/dist/index.html. Run bun run build:web first.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = listFiles(webDistDir).sort((a, b) => a.localeCompare(b));
|
||||||
|
if (files.length === 0) {
|
||||||
|
throw new Error(`No files found in web/dist: ${webDistDir}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imports: string[] = [];
|
||||||
|
const manifestLines: string[] = [];
|
||||||
|
|
||||||
|
files.forEach((filePath, index) => {
|
||||||
|
const relativeToDist = toPosixPath(relative(webDistDir, filePath));
|
||||||
|
const requestPath = `/${relativeToDist}`;
|
||||||
|
const importPath = toPosixPath(relative(outputDir, filePath));
|
||||||
|
const importName = `asset${index}`;
|
||||||
|
const mimeType = resolveMimeType(filePath);
|
||||||
|
|
||||||
|
imports.push(`import ${importName} from '${importPath}' assert { type: 'file' };`);
|
||||||
|
manifestLines.push(` { path: '${requestPath}', sourcePath: ${importName}, mimeType: '${mimeType}' },`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = [
|
||||||
|
'// This file is generated by server/scripts/generate-embedded-web-assets.ts.',
|
||||||
|
'// Do not edit by hand.',
|
||||||
|
'',
|
||||||
|
...imports,
|
||||||
|
'',
|
||||||
|
'export interface EmbeddedWebAsset {',
|
||||||
|
' path: string;',
|
||||||
|
' sourcePath: string;',
|
||||||
|
' mimeType: string;',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
'export const embeddedAssets: EmbeddedWebAsset[] = [',
|
||||||
|
...manifestLines,
|
||||||
|
'];',
|
||||||
|
''
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
writeFileSync(outputPath, output, 'utf-8');
|
||||||
|
console.log(`[embedded-assets] Wrote ${files.length} assets to ${outputPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Stub types for embeddedAssets.generated.ts when the manifest is not generated.
|
||||||
|
declare module './embeddedAssets.generated' {
|
||||||
|
export interface EmbeddedWebAsset {
|
||||||
|
path: string;
|
||||||
|
sourcePath: string;
|
||||||
|
mimeType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const embeddedAssets: EmbeddedWebAsset[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { EmbeddedWebAsset } from './embeddedAssets.generated';
|
||||||
|
|
||||||
|
let embeddedAssetMap: Map<string, EmbeddedWebAsset> | null = null;
|
||||||
|
|
||||||
|
export type { EmbeddedWebAsset };
|
||||||
|
|
||||||
|
export async function loadEmbeddedAssetMap(): Promise<Map<string, EmbeddedWebAsset>> {
|
||||||
|
if (embeddedAssetMap) {
|
||||||
|
return embeddedAssetMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { embeddedAssets } = await import('./embeddedAssets.generated');
|
||||||
|
embeddedAssetMap = new Map(embeddedAssets.map((asset) => [asset.path, asset]));
|
||||||
|
return embeddedAssetMap;
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import type { SSEManager } from '../sse/sseManager'
|
|||||||
import type { Server as BunServer } from 'bun'
|
import type { Server as BunServer } from 'bun'
|
||||||
import type { Server as SocketEngine } from '@socket.io/bun-engine'
|
import type { Server as SocketEngine } from '@socket.io/bun-engine'
|
||||||
import type { WebSocketData } from '@socket.io/bun-engine'
|
import type { WebSocketData } from '@socket.io/bun-engine'
|
||||||
|
import { loadEmbeddedAssetMap, type EmbeddedWebAsset } from './embeddedAssets'
|
||||||
|
|
||||||
function findWebappDistDir(): { distDir: string; indexHtmlPath: string } {
|
function findWebappDistDir(): { distDir: string; indexHtmlPath: string } {
|
||||||
const candidates = [
|
const candidates = [
|
||||||
@@ -38,10 +39,19 @@ function findWebappDistDir(): { distDir: string; indexHtmlPath: string } {
|
|||||||
return { distDir, indexHtmlPath: join(distDir, 'index.html') }
|
return { distDir, indexHtmlPath: join(distDir, 'index.html') }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serveEmbeddedAsset(asset: EmbeddedWebAsset): Response {
|
||||||
|
return new Response(Bun.file(asset.sourcePath), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': asset.mimeType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function createWebApp(options: {
|
function createWebApp(options: {
|
||||||
getSyncEngine: () => SyncEngine | null
|
getSyncEngine: () => SyncEngine | null
|
||||||
getSseManager: () => SSEManager | null
|
getSseManager: () => SSEManager | null
|
||||||
jwtSecret: Uint8Array
|
jwtSecret: Uint8Array
|
||||||
|
embeddedAssetMap: Map<string, EmbeddedWebAsset> | null
|
||||||
}): Hono<WebAppEnv> {
|
}): Hono<WebAppEnv> {
|
||||||
const app = new Hono<WebAppEnv>()
|
const app = new Hono<WebAppEnv>()
|
||||||
|
|
||||||
@@ -69,6 +79,51 @@ function createWebApp(options: {
|
|||||||
app.route('/api', createMachinesRoutes(options.getSyncEngine))
|
app.route('/api', createMachinesRoutes(options.getSyncEngine))
|
||||||
app.route('/api', createGitRoutes(options.getSyncEngine))
|
app.route('/api', createGitRoutes(options.getSyncEngine))
|
||||||
|
|
||||||
|
if (options.embeddedAssetMap) {
|
||||||
|
const embeddedAssetMap = options.embeddedAssetMap
|
||||||
|
const indexHtmlAsset = embeddedAssetMap.get('/index.html')
|
||||||
|
|
||||||
|
if (!indexHtmlAsset) {
|
||||||
|
app.get('*', (c) => {
|
||||||
|
return c.text(
|
||||||
|
'Embedded Mini App is missing index.html. Rebuild the executable after running bun run build:web.',
|
||||||
|
503
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use('*', async (c, next) => {
|
||||||
|
if (c.req.path.startsWith('/api')) {
|
||||||
|
await next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.req.method !== 'GET' && c.req.method !== 'HEAD') {
|
||||||
|
await next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset = embeddedAssetMap.get(c.req.path)
|
||||||
|
if (asset) {
|
||||||
|
return serveEmbeddedAsset(asset)
|
||||||
|
}
|
||||||
|
|
||||||
|
await next()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('*', async (c, next) => {
|
||||||
|
if (c.req.path.startsWith('/api')) {
|
||||||
|
await next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return serveEmbeddedAsset(indexHtmlAsset)
|
||||||
|
})
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
const { distDir, indexHtmlPath } = findWebappDistDir()
|
const { distDir, indexHtmlPath } = findWebappDistDir()
|
||||||
|
|
||||||
if (!existsSync(indexHtmlPath)) {
|
if (!existsSync(indexHtmlPath)) {
|
||||||
@@ -110,10 +165,12 @@ export async function startWebServer(options: {
|
|||||||
jwtSecret: Uint8Array
|
jwtSecret: Uint8Array
|
||||||
socketEngine: SocketEngine
|
socketEngine: SocketEngine
|
||||||
}): Promise<BunServer<WebSocketData>> {
|
}): Promise<BunServer<WebSocketData>> {
|
||||||
|
const embeddedAssetMap = Bun.isCompiled ? await loadEmbeddedAssetMap() : null
|
||||||
const app = createWebApp({
|
const app = createWebApp({
|
||||||
getSyncEngine: options.getSyncEngine,
|
getSyncEngine: options.getSyncEngine,
|
||||||
getSseManager: options.getSseManager,
|
getSseManager: options.getSseManager,
|
||||||
jwtSecret: options.jwtSecret
|
jwtSecret: options.jwtSecret,
|
||||||
|
embeddedAssetMap
|
||||||
})
|
})
|
||||||
|
|
||||||
const socketHandler = options.socketEngine.handler()
|
const socketHandler = options.socketEngine.handler()
|
||||||
|
|||||||
@@ -10,5 +10,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"],
|
||||||
"include": ["src/**/*.ts"]
|
"include": ["src/**/*.ts", "src/**/*.d.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user