mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
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
114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
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();
|