@@ -3,6 +3,36 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from
33import { resolve , relative , join } from "path" ;
44import { getAixyzConfig } from "@aixyz/config" ;
55
6+ /**
7+ * Generate code that scans for static assets at startup and builds a `Bun.serve({ static })` map.
8+ * At runtime, `import.meta.dir` resolves to the directory containing the bundled server.js (or compiled binary).
9+ * Static files live alongside it: `icon.png` and `public/` (copied there by the build step).
10+ */
11+ function generateStaticSetup ( ) : { imports : string ; setup : string } {
12+ const imports = [
13+ 'import { existsSync as __existsSync, readdirSync as __readdirSync } from "node:fs";' ,
14+ 'import { resolve as __resolve, join as __join } from "node:path";' ,
15+ ] . join ( "\n" ) ;
16+
17+ const setup = [
18+ `const __aixyzStatic: Record<string, Response> = {};` ,
19+ `const __iconPath = __resolve(import.meta.dir, "icon.png");` ,
20+ `if (__existsSync(__iconPath)) __aixyzStatic["/icon.png"] = new Response(Bun.file(__iconPath));` ,
21+ `const __publicDir = __resolve(import.meta.dir, "public");` ,
22+ `if (__existsSync(__publicDir)) {` ,
23+ ` (function __walk(dir: string, prefix: string) {` ,
24+ ` for (const e of __readdirSync(dir, { withFileTypes: true })) {` ,
25+ ` const p = __join(dir, e.name);` ,
26+ ` if (e.isFile()) __aixyzStatic[prefix + "/" + e.name] = new Response(Bun.file(p));` ,
27+ ` else if (e.isDirectory()) __walk(p, prefix + "/" + e.name);` ,
28+ ` }` ,
29+ ` })(__publicDir, "");` ,
30+ `}` ,
31+ ] . join ( "\n" ) ;
32+
33+ return { imports, setup } ;
34+ }
35+
636export function AixyzServerPlugin (
737 entrypoint : string ,
838 mode : "vercel" | "standalone" | "executable" ,
@@ -27,23 +57,26 @@ export function AixyzServerPlugin(
2757 const identifierRe = / e x p o r t \s + d e f a u l t \s + ( \w + ) \s * ; / ;
2858 const expressionRe = / e x p o r t \s + d e f a u l t \s + / ;
2959
60+ const { imports : staticImports , setup : staticSetup } = generateStaticSetup ( ) ;
61+ const bunServe = ( fetchExpr : string ) =>
62+ `${ staticSetup } \nconst __server = Bun.serve({ port: parseInt(process.env.PORT || "3000", 10), static: __aixyzStatic, fetch: ${ fetchExpr } } as Parameters<typeof Bun.serve>[0]);\nconsole.log(\`Server listening on port \${__server.port}\`);` ;
63+
3064 let transformed : string ;
3165 const identifierMatch = source . match ( identifierRe ) ;
3266 if ( identifierMatch ) {
33- transformed = source . replace (
34- identifierRe ,
35- `const __server = Bun.serve({ port: parseInt(process.env.PORT || "3000", 10), fetch: ${ identifierMatch [ 1 ] } .fetch });\nconsole.log(\`Server listening on port \${__server.port}\`);` ,
36- ) ;
67+ transformed = source . replace ( identifierRe , bunServe ( `${ identifierMatch [ 1 ] } .fetch` ) ) ;
3768 } else if ( expressionRe . test ( source ) ) {
3869 transformed = source . replace ( expressionRe , `const __app = ` ) ;
39- transformed += `\nconst __server = Bun.serve({ port: parseInt(process.env.PORT || "3000", 10), fetch: __app.fetch });\nconsole.log(\`Server listening on port \${__server.port}\`);` ;
70+ transformed += "\n" + bunServe ( " __app.fetch" ) ;
4071 } else {
4172 throw new Error (
4273 `[aixyz] Could not find \`export default\` in entrypoint ${ args . path } . ` +
4374 `Standalone and executable builds require the server entrypoint to use \`export default app;\` ` +
4475 `or \`export default new AixyzApp({...});\`.` ,
4576 ) ;
4677 }
78+
79+ transformed = staticImports + "\n" + transformed ;
4780 return { contents : transformed , loader : "ts" } ;
4881 } ) ;
4982 } ,
0 commit comments