next-bun-compile 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ramon Malcolm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # next-bun-compile
2
+
3
+ A Next.js Build Adapter that compiles your app into a single-file [Bun](https://bun.sh) executable.
4
+
5
+ One command. One binary. No runtime dependencies.
6
+
7
+ ```bash
8
+ next build # → ./server (single executable with embedded assets)
9
+ ```
10
+
11
+ ## Requirements
12
+
13
+ - [Bun](https://bun.sh) >= 1.3
14
+ - [Next.js](https://nextjs.org) >= 16.0.0
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ bun add -D next-bun-compile
20
+ ```
21
+
22
+ ## Setup
23
+
24
+ Add the adapter to your `next.config.ts`:
25
+
26
+ ```ts
27
+ import type { NextConfig } from "next";
28
+
29
+ const nextConfig: NextConfig = {
30
+ experimental: {
31
+ adapterPath: require.resolve("next-bun-compile"),
32
+ },
33
+ };
34
+
35
+ export default nextConfig;
36
+ ```
37
+
38
+ Update your build script in `package.json`:
39
+
40
+ ```json
41
+ {
42
+ "scripts": {
43
+ "build": "next build && next-bun-compile"
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ```bash
51
+ bun run build # Builds Next.js + compiles to ./server
52
+ ./server # Starts on port 3000
53
+ ```
54
+
55
+ The binary is fully self-contained — static assets, public files, and the Next.js server are all embedded. Just copy it anywhere and run.
56
+
57
+ ### Environment Variables
58
+
59
+ | Variable | Default | Description |
60
+ |---|---|---|
61
+ | `PORT` | `3000` | Server port |
62
+ | `HOSTNAME` | `0.0.0.0` | Server hostname |
63
+ | `KEEP_ALIVE_TIMEOUT` | — | HTTP keep-alive timeout (ms) |
64
+
65
+ ## How It Works
66
+
67
+ 1. **Adapter hook** — `modifyConfig()` sets `output: "standalone"` automatically so you don't need to configure it
68
+ 2. **Asset discovery** — Scans `.next/static/` and `public/` for all static files
69
+ 3. **Code generation** — Creates a `server-entry.js` that:
70
+ - Embeds all assets into the binary via Bun's `import ... with { type: "file" }`
71
+ - Extracts them to disk on first run
72
+ - Fixes `__dirname` for compiled binary context
73
+ - Starts the Next.js server
74
+ 4. **Compilation** — Runs `bun build --compile` with `--define` flags to eliminate dead code branches (dev-only modules, non-turbo runtimes)
75
+
76
+ ### Module Stubs
77
+
78
+ Some modules can't be resolved at compile time but are never reached in production (dev servers, optional dependencies). The adapter creates no-op stubs for these **only if** the real module isn't installed. If you actually use `@opentelemetry/api` or `critters`, the real package gets bundled instead.
79
+
80
+ ## Support
81
+
82
+ If this saved you time, consider supporting the project:
83
+
84
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-yellow?logo=buy-me-a-coffee&logoColor=white)](https://buymeacoffee.com/ramonmalcolm)
85
+
86
+ ## License
87
+
88
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
+
5
+ // src/generate.ts
6
+ import {
7
+ writeFileSync,
8
+ readFileSync,
9
+ existsSync,
10
+ readdirSync,
11
+ statSync,
12
+ mkdirSync
13
+ } from "node:fs";
14
+ import { join, relative } from "node:path";
15
+ import { createHash } from "node:crypto";
16
+ function walkDir(dir, base = dir) {
17
+ const results = [];
18
+ if (!existsSync(dir))
19
+ return results;
20
+ for (const entry of readdirSync(dir)) {
21
+ const full = join(dir, entry);
22
+ if (statSync(full).isDirectory()) {
23
+ results.push(...walkDir(full, base));
24
+ } else {
25
+ results.push({ absolutePath: full, relativePath: relative(base, full) });
26
+ }
27
+ }
28
+ return results;
29
+ }
30
+ function toVarName(filePath) {
31
+ const hash = createHash("md5").update(filePath).digest("hex").slice(0, 6);
32
+ const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
33
+ return `asset_${safe}_${hash}`;
34
+ }
35
+ function generateStubs(standaloneDir) {
36
+ const stubs = [
37
+ {
38
+ path: "node_modules/next/dist/server/dev/next-dev-server.js",
39
+ content: "module.exports = { default: null };"
40
+ },
41
+ {
42
+ path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
43
+ content: "module.exports = {};"
44
+ },
45
+ {
46
+ path: "node_modules/@opentelemetry/api/index.js",
47
+ content: "throw new Error('not installed');"
48
+ },
49
+ {
50
+ path: "node_modules/critters/index.js",
51
+ content: "module.exports = {};"
52
+ }
53
+ ];
54
+ let count = 0;
55
+ for (const stub of stubs) {
56
+ const fullPath = join(standaloneDir, stub.path);
57
+ if (!existsSync(fullPath)) {
58
+ const dir = join(fullPath, "..");
59
+ if (!existsSync(dir)) {
60
+ mkdirSync(dir, { recursive: true });
61
+ }
62
+ writeFileSync(fullPath, stub.content);
63
+ count++;
64
+ }
65
+ }
66
+ if (count > 0) {
67
+ console.log(`next-bun-compile: Created ${count} module stubs`);
68
+ }
69
+ }
70
+ function generateEntryPoint(options) {
71
+ const { standaloneDir, distDir, projectDir } = options;
72
+ generateStubs(standaloneDir);
73
+ const staticDir = join(distDir, "static");
74
+ const staticFiles = walkDir(staticDir).map((f) => ({
75
+ ...f,
76
+ urlPath: `/_next/static/${f.relativePath}`
77
+ }));
78
+ const publicDir = join(projectDir, "public");
79
+ const publicFiles = walkDir(publicDir).map((f) => ({
80
+ ...f,
81
+ urlPath: `/${f.relativePath}`
82
+ }));
83
+ const allAssets = [...staticFiles, ...publicFiles];
84
+ console.log(`next-bun-compile: Found ${staticFiles.length} static + ${publicFiles.length} public = ${allAssets.length} assets`);
85
+ const imports = [];
86
+ const mapEntries = [];
87
+ for (const asset of allAssets) {
88
+ const varName = toVarName(asset.urlPath);
89
+ const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
90
+ imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
91
+ mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
92
+ }
93
+ writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
94
+ `)}
95
+ export const assetMap = new Map([
96
+ ${mapEntries.join(`
97
+ `)}
98
+ ]);
99
+ `);
100
+ const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
101
+ const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
102
+ if (!configMatch) {
103
+ throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
104
+ }
105
+ const assetExtractions = allAssets.map((a) => {
106
+ const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
107
+ return [a.urlPath, diskPath];
108
+ });
109
+ const serverEntry = `import { assetMap } from "./assets.generated.js";
110
+ const path = require("path");
111
+ const fs = require("fs");
112
+
113
+ const baseDir = path.dirname(process.execPath);
114
+ process.chdir(baseDir);
115
+ process.env.NODE_ENV = "production";
116
+
117
+ const nextConfig = ${configMatch[1]};
118
+ process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig);
119
+
120
+ const currentPort = parseInt(process.env.PORT, 10) || 3000;
121
+ const hostname = process.env.HOSTNAME || "0.0.0.0";
122
+ let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
123
+ if (Number.isNaN(keepAliveTimeout) || !Number.isFinite(keepAliveTimeout) || keepAliveTimeout < 0) {
124
+ keepAliveTimeout = undefined;
125
+ }
126
+
127
+ const extractions = ${JSON.stringify(assetExtractions)};
128
+ async function extractAssets() {
129
+ let n = 0;
130
+ for (const [urlPath, diskPath] of extractions) {
131
+ const fullPath = path.join(baseDir, diskPath);
132
+ if (fs.existsSync(fullPath)) continue;
133
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
134
+ const embedded = assetMap.get(urlPath);
135
+ if (embedded) { await Bun.write(fullPath, Bun.file(embedded)); n++; }
136
+ }
137
+ if (n > 0) console.log(\`Extracted \${n} assets\`);
138
+ }
139
+
140
+ extractAssets().then(() => {
141
+ require("next");
142
+ const { startServer } = require("next/dist/server/lib/start-server");
143
+ return startServer({
144
+ dir: baseDir, isDev: false, config: nextConfig,
145
+ hostname, port: currentPort, allowRetry: false, keepAliveTimeout,
146
+ });
147
+ }).catch((err) => { console.error(err); process.exit(1); });
148
+ `;
149
+ writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
150
+ console.log("next-bun-compile: Generated server-entry.js + assets.generated.js");
151
+ }
152
+
153
+ // src/compile.ts
154
+ import { execFileSync } from "node:child_process";
155
+ import { join as join2 } from "node:path";
156
+ function compile(options) {
157
+ const { standaloneDir, outfile } = options;
158
+ const entryPoint = join2(standaloneDir, "server-entry.js");
159
+ const args = [
160
+ "build",
161
+ entryPoint,
162
+ "--production",
163
+ "--compile",
164
+ "--minify",
165
+ "--bytecode",
166
+ "--sourcemap",
167
+ "--define",
168
+ "process.env.TURBOPACK=1",
169
+ "--define",
170
+ "process.env.__NEXT_EXPERIMENTAL_REACT=",
171
+ "--define",
172
+ 'process.env.NEXT_RUNTIME="nodejs"',
173
+ "--outfile",
174
+ outfile
175
+ ];
176
+ console.log(`next-bun-compile: Compiling to ${outfile}...`);
177
+ execFileSync("bun", args, { stdio: "inherit" });
178
+ console.log(`next-bun-compile: Done → ${outfile}`);
179
+ }
180
+
181
+ // src/cli.ts
182
+ import { existsSync as existsSync2 } from "node:fs";
183
+ import { join as join3, resolve } from "node:path";
184
+ var projectDir = resolve(".");
185
+ var distDir = join3(projectDir, ".next");
186
+ var standaloneDir = join3(distDir, "standalone");
187
+ if (!existsSync2(standaloneDir)) {
188
+ console.error('next-bun-compile: No standalone output found. Run "next build" first with output: "standalone" in next.config.ts.');
189
+ process.exit(1);
190
+ }
191
+ var ctxPath = join3(distDir, "bun-compile-ctx.json");
192
+ if (existsSync2(ctxPath)) {
193
+ console.log("next-bun-compile: Using build context from adapter");
194
+ }
195
+ generateEntryPoint({ standaloneDir, distDir, projectDir });
196
+ compile({ standaloneDir, outfile: join3(projectDir, "server") });
@@ -0,0 +1,33 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/compile.ts
5
+ import { execFileSync } from "node:child_process";
6
+ import { join } from "node:path";
7
+ function compile(options) {
8
+ const { standaloneDir, outfile } = options;
9
+ const entryPoint = join(standaloneDir, "server-entry.js");
10
+ const args = [
11
+ "build",
12
+ entryPoint,
13
+ "--production",
14
+ "--compile",
15
+ "--minify",
16
+ "--bytecode",
17
+ "--sourcemap",
18
+ "--define",
19
+ "process.env.TURBOPACK=1",
20
+ "--define",
21
+ "process.env.__NEXT_EXPERIMENTAL_REACT=",
22
+ "--define",
23
+ 'process.env.NEXT_RUNTIME="nodejs"',
24
+ "--outfile",
25
+ outfile
26
+ ];
27
+ console.log(`next-bun-compile: Compiling to ${outfile}...`);
28
+ execFileSync("bun", args, { stdio: "inherit" });
29
+ console.log(`next-bun-compile: Done → ${outfile}`);
30
+ }
31
+ export {
32
+ compile
33
+ };
@@ -0,0 +1,153 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/generate.ts
5
+ import {
6
+ writeFileSync,
7
+ readFileSync,
8
+ existsSync,
9
+ readdirSync,
10
+ statSync,
11
+ mkdirSync
12
+ } from "node:fs";
13
+ import { join, relative } from "node:path";
14
+ import { createHash } from "node:crypto";
15
+ function walkDir(dir, base = dir) {
16
+ const results = [];
17
+ if (!existsSync(dir))
18
+ return results;
19
+ for (const entry of readdirSync(dir)) {
20
+ const full = join(dir, entry);
21
+ if (statSync(full).isDirectory()) {
22
+ results.push(...walkDir(full, base));
23
+ } else {
24
+ results.push({ absolutePath: full, relativePath: relative(base, full) });
25
+ }
26
+ }
27
+ return results;
28
+ }
29
+ function toVarName(filePath) {
30
+ const hash = createHash("md5").update(filePath).digest("hex").slice(0, 6);
31
+ const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
32
+ return `asset_${safe}_${hash}`;
33
+ }
34
+ function generateStubs(standaloneDir) {
35
+ const stubs = [
36
+ {
37
+ path: "node_modules/next/dist/server/dev/next-dev-server.js",
38
+ content: "module.exports = { default: null };"
39
+ },
40
+ {
41
+ path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
42
+ content: "module.exports = {};"
43
+ },
44
+ {
45
+ path: "node_modules/@opentelemetry/api/index.js",
46
+ content: "throw new Error('not installed');"
47
+ },
48
+ {
49
+ path: "node_modules/critters/index.js",
50
+ content: "module.exports = {};"
51
+ }
52
+ ];
53
+ let count = 0;
54
+ for (const stub of stubs) {
55
+ const fullPath = join(standaloneDir, stub.path);
56
+ if (!existsSync(fullPath)) {
57
+ const dir = join(fullPath, "..");
58
+ if (!existsSync(dir)) {
59
+ mkdirSync(dir, { recursive: true });
60
+ }
61
+ writeFileSync(fullPath, stub.content);
62
+ count++;
63
+ }
64
+ }
65
+ if (count > 0) {
66
+ console.log(`next-bun-compile: Created ${count} module stubs`);
67
+ }
68
+ }
69
+ function generateEntryPoint(options) {
70
+ const { standaloneDir, distDir, projectDir } = options;
71
+ generateStubs(standaloneDir);
72
+ const staticDir = join(distDir, "static");
73
+ const staticFiles = walkDir(staticDir).map((f) => ({
74
+ ...f,
75
+ urlPath: `/_next/static/${f.relativePath}`
76
+ }));
77
+ const publicDir = join(projectDir, "public");
78
+ const publicFiles = walkDir(publicDir).map((f) => ({
79
+ ...f,
80
+ urlPath: `/${f.relativePath}`
81
+ }));
82
+ const allAssets = [...staticFiles, ...publicFiles];
83
+ console.log(`next-bun-compile: Found ${staticFiles.length} static + ${publicFiles.length} public = ${allAssets.length} assets`);
84
+ const imports = [];
85
+ const mapEntries = [];
86
+ for (const asset of allAssets) {
87
+ const varName = toVarName(asset.urlPath);
88
+ const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
89
+ imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
90
+ mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
91
+ }
92
+ writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
93
+ `)}
94
+ export const assetMap = new Map([
95
+ ${mapEntries.join(`
96
+ `)}
97
+ ]);
98
+ `);
99
+ const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
100
+ const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
101
+ if (!configMatch) {
102
+ throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
103
+ }
104
+ const assetExtractions = allAssets.map((a) => {
105
+ const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
106
+ return [a.urlPath, diskPath];
107
+ });
108
+ const serverEntry = `import { assetMap } from "./assets.generated.js";
109
+ const path = require("path");
110
+ const fs = require("fs");
111
+
112
+ const baseDir = path.dirname(process.execPath);
113
+ process.chdir(baseDir);
114
+ process.env.NODE_ENV = "production";
115
+
116
+ const nextConfig = ${configMatch[1]};
117
+ process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig);
118
+
119
+ const currentPort = parseInt(process.env.PORT, 10) || 3000;
120
+ const hostname = process.env.HOSTNAME || "0.0.0.0";
121
+ let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
122
+ if (Number.isNaN(keepAliveTimeout) || !Number.isFinite(keepAliveTimeout) || keepAliveTimeout < 0) {
123
+ keepAliveTimeout = undefined;
124
+ }
125
+
126
+ const extractions = ${JSON.stringify(assetExtractions)};
127
+ async function extractAssets() {
128
+ let n = 0;
129
+ for (const [urlPath, diskPath] of extractions) {
130
+ const fullPath = path.join(baseDir, diskPath);
131
+ if (fs.existsSync(fullPath)) continue;
132
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
133
+ const embedded = assetMap.get(urlPath);
134
+ if (embedded) { await Bun.write(fullPath, Bun.file(embedded)); n++; }
135
+ }
136
+ if (n > 0) console.log(\`Extracted \${n} assets\`);
137
+ }
138
+
139
+ extractAssets().then(() => {
140
+ require("next");
141
+ const { startServer } = require("next/dist/server/lib/start-server");
142
+ return startServer({
143
+ dir: baseDir, isDev: false, config: nextConfig,
144
+ hostname, port: currentPort, allowRetry: false, keepAliveTimeout,
145
+ });
146
+ }).catch((err) => { console.error(err); process.exit(1); });
147
+ `;
148
+ writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
149
+ console.log("next-bun-compile: Generated server-entry.js + assets.generated.js");
150
+ }
151
+ export {
152
+ generateEntryPoint
153
+ };
package/dist/index.js ADDED
@@ -0,0 +1,204 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/generate.ts
5
+ import {
6
+ writeFileSync,
7
+ readFileSync,
8
+ existsSync,
9
+ readdirSync,
10
+ statSync,
11
+ mkdirSync
12
+ } from "node:fs";
13
+ import { join, relative } from "node:path";
14
+ import { createHash } from "node:crypto";
15
+ function walkDir(dir, base = dir) {
16
+ const results = [];
17
+ if (!existsSync(dir))
18
+ return results;
19
+ for (const entry of readdirSync(dir)) {
20
+ const full = join(dir, entry);
21
+ if (statSync(full).isDirectory()) {
22
+ results.push(...walkDir(full, base));
23
+ } else {
24
+ results.push({ absolutePath: full, relativePath: relative(base, full) });
25
+ }
26
+ }
27
+ return results;
28
+ }
29
+ function toVarName(filePath) {
30
+ const hash = createHash("md5").update(filePath).digest("hex").slice(0, 6);
31
+ const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
32
+ return `asset_${safe}_${hash}`;
33
+ }
34
+ function generateStubs(standaloneDir) {
35
+ const stubs = [
36
+ {
37
+ path: "node_modules/next/dist/server/dev/next-dev-server.js",
38
+ content: "module.exports = { default: null };"
39
+ },
40
+ {
41
+ path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
42
+ content: "module.exports = {};"
43
+ },
44
+ {
45
+ path: "node_modules/@opentelemetry/api/index.js",
46
+ content: "throw new Error('not installed');"
47
+ },
48
+ {
49
+ path: "node_modules/critters/index.js",
50
+ content: "module.exports = {};"
51
+ }
52
+ ];
53
+ let count = 0;
54
+ for (const stub of stubs) {
55
+ const fullPath = join(standaloneDir, stub.path);
56
+ if (!existsSync(fullPath)) {
57
+ const dir = join(fullPath, "..");
58
+ if (!existsSync(dir)) {
59
+ mkdirSync(dir, { recursive: true });
60
+ }
61
+ writeFileSync(fullPath, stub.content);
62
+ count++;
63
+ }
64
+ }
65
+ if (count > 0) {
66
+ console.log(`next-bun-compile: Created ${count} module stubs`);
67
+ }
68
+ }
69
+ function generateEntryPoint(options) {
70
+ const { standaloneDir, distDir, projectDir } = options;
71
+ generateStubs(standaloneDir);
72
+ const staticDir = join(distDir, "static");
73
+ const staticFiles = walkDir(staticDir).map((f) => ({
74
+ ...f,
75
+ urlPath: `/_next/static/${f.relativePath}`
76
+ }));
77
+ const publicDir = join(projectDir, "public");
78
+ const publicFiles = walkDir(publicDir).map((f) => ({
79
+ ...f,
80
+ urlPath: `/${f.relativePath}`
81
+ }));
82
+ const allAssets = [...staticFiles, ...publicFiles];
83
+ console.log(`next-bun-compile: Found ${staticFiles.length} static + ${publicFiles.length} public = ${allAssets.length} assets`);
84
+ const imports = [];
85
+ const mapEntries = [];
86
+ for (const asset of allAssets) {
87
+ const varName = toVarName(asset.urlPath);
88
+ const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
89
+ imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
90
+ mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
91
+ }
92
+ writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
93
+ `)}
94
+ export const assetMap = new Map([
95
+ ${mapEntries.join(`
96
+ `)}
97
+ ]);
98
+ `);
99
+ const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
100
+ const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
101
+ if (!configMatch) {
102
+ throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
103
+ }
104
+ const assetExtractions = allAssets.map((a) => {
105
+ const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
106
+ return [a.urlPath, diskPath];
107
+ });
108
+ const serverEntry = `import { assetMap } from "./assets.generated.js";
109
+ const path = require("path");
110
+ const fs = require("fs");
111
+
112
+ const baseDir = path.dirname(process.execPath);
113
+ process.chdir(baseDir);
114
+ process.env.NODE_ENV = "production";
115
+
116
+ const nextConfig = ${configMatch[1]};
117
+ process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig);
118
+
119
+ const currentPort = parseInt(process.env.PORT, 10) || 3000;
120
+ const hostname = process.env.HOSTNAME || "0.0.0.0";
121
+ let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
122
+ if (Number.isNaN(keepAliveTimeout) || !Number.isFinite(keepAliveTimeout) || keepAliveTimeout < 0) {
123
+ keepAliveTimeout = undefined;
124
+ }
125
+
126
+ const extractions = ${JSON.stringify(assetExtractions)};
127
+ async function extractAssets() {
128
+ let n = 0;
129
+ for (const [urlPath, diskPath] of extractions) {
130
+ const fullPath = path.join(baseDir, diskPath);
131
+ if (fs.existsSync(fullPath)) continue;
132
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
133
+ const embedded = assetMap.get(urlPath);
134
+ if (embedded) { await Bun.write(fullPath, Bun.file(embedded)); n++; }
135
+ }
136
+ if (n > 0) console.log(\`Extracted \${n} assets\`);
137
+ }
138
+
139
+ extractAssets().then(() => {
140
+ require("next");
141
+ const { startServer } = require("next/dist/server/lib/start-server");
142
+ return startServer({
143
+ dir: baseDir, isDev: false, config: nextConfig,
144
+ hostname, port: currentPort, allowRetry: false, keepAliveTimeout,
145
+ });
146
+ }).catch((err) => { console.error(err); process.exit(1); });
147
+ `;
148
+ writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
149
+ console.log("next-bun-compile: Generated server-entry.js + assets.generated.js");
150
+ }
151
+
152
+ // src/compile.ts
153
+ import { execFileSync } from "node:child_process";
154
+ import { join as join2 } from "node:path";
155
+ function compile(options) {
156
+ const { standaloneDir, outfile } = options;
157
+ const entryPoint = join2(standaloneDir, "server-entry.js");
158
+ const args = [
159
+ "build",
160
+ entryPoint,
161
+ "--production",
162
+ "--compile",
163
+ "--minify",
164
+ "--bytecode",
165
+ "--sourcemap",
166
+ "--define",
167
+ "process.env.TURBOPACK=1",
168
+ "--define",
169
+ "process.env.__NEXT_EXPERIMENTAL_REACT=",
170
+ "--define",
171
+ 'process.env.NEXT_RUNTIME="nodejs"',
172
+ "--outfile",
173
+ outfile
174
+ ];
175
+ console.log(`next-bun-compile: Compiling to ${outfile}...`);
176
+ execFileSync("bun", args, { stdio: "inherit" });
177
+ console.log(`next-bun-compile: Done → ${outfile}`);
178
+ }
179
+
180
+ // src/index.ts
181
+ import { join as join3 } from "node:path";
182
+ var adapter = {
183
+ name: "next-bun-compile",
184
+ modifyConfig(config) {
185
+ if (config.output !== "standalone") {
186
+ console.warn('next-bun-compile: Setting output to "standalone" (required for compilation)');
187
+ config.output = "standalone";
188
+ }
189
+ return config;
190
+ },
191
+ async onBuildComplete(ctx) {
192
+ const { writeFileSync: writeFileSync2 } = await import("node:fs");
193
+ writeFileSync2(join3(ctx.distDir, "bun-compile-ctx.json"), JSON.stringify({
194
+ distDir: ctx.distDir,
195
+ projectDir: ctx.projectDir
196
+ }));
197
+ }
198
+ };
199
+ var src_default = adapter;
200
+ export {
201
+ generateEntryPoint,
202
+ src_default as default,
203
+ compile
204
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "next-bun-compile",
3
+ "version": "0.1.0",
4
+ "description": "Next.js Build Adapter that compiles your app into a Bun single-file executable",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "next-bun-compile": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "bun build src/index.ts src/generate.ts src/compile.ts src/cli.ts --outdir dist --target node",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "peerDependencies": {
25
+ "next": ">=16.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "next": "16.1.6",
29
+ "@types/node": "^20",
30
+ "typescript": "^5"
31
+ },
32
+ "keywords": [
33
+ "nextjs",
34
+ "bun",
35
+ "compile",
36
+ "standalone",
37
+ "executable",
38
+ "adapter"
39
+ ],
40
+ "license": "MIT",
41
+ "homepage": "https://github.com/ramonmalcolm10/next-bun-compile#readme",
42
+ "bugs": {
43
+ "url": "https://github.com/ramonmalcolm10/next-bun-compile/issues"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/ramonmalcolm10/next-bun-compile.git"
48
+ }
49
+ }