next-bun-compile 0.8.0 → 0.9.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/dist/cli.js +1 -1
- package/dist/{index-34djf48r.js → index-45tzedws.js} +79 -48
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -7,21 +7,17 @@ import {
|
|
|
7
7
|
statSync,
|
|
8
8
|
lstatSync,
|
|
9
9
|
realpathSync,
|
|
10
|
-
mkdirSync
|
|
10
|
+
mkdirSync,
|
|
11
|
+
copyFileSync
|
|
11
12
|
} from "node:fs";
|
|
12
13
|
import { join, relative, basename } from "node:path";
|
|
13
14
|
import { createHash } from "node:crypto";
|
|
14
15
|
function readAssetPrefix(distDir) {
|
|
15
|
-
const ctxPath = join(distDir, "bun-compile-ctx.json");
|
|
16
|
-
if (existsSync(ctxPath)) {
|
|
17
|
-
return JSON.parse(readFileSync(ctxPath, "utf-8")).assetPrefix ?? "";
|
|
18
|
-
}
|
|
19
16
|
const rsfPath = join(distDir, "required-server-files.json");
|
|
20
|
-
if (existsSync(rsfPath))
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return "";
|
|
17
|
+
if (!existsSync(rsfPath))
|
|
18
|
+
return "";
|
|
19
|
+
const rsf = JSON.parse(readFileSync(rsfPath, "utf-8"));
|
|
20
|
+
return rsf.config?.assetPrefix ?? "";
|
|
25
21
|
}
|
|
26
22
|
function tryStat(p) {
|
|
27
23
|
try {
|
|
@@ -51,7 +47,7 @@ function walkDir(dir, base = dir) {
|
|
|
51
47
|
return results;
|
|
52
48
|
}
|
|
53
49
|
function toVarName(filePath) {
|
|
54
|
-
const hash = createHash("
|
|
50
|
+
const hash = createHash("sha256").update(filePath).digest("hex").slice(0, 6);
|
|
55
51
|
const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
|
|
56
52
|
return `asset_${safe}_${hash}`;
|
|
57
53
|
}
|
|
@@ -307,14 +303,14 @@ function validateAliasResolutions(aliases, resolutions) {
|
|
|
307
303
|
return unresolved;
|
|
308
304
|
}
|
|
309
305
|
function rewriteTurbopackAliases(standaloneNextDir, aliases, resolutions) {
|
|
306
|
+
const rewrittenPaths = [];
|
|
310
307
|
if (aliases.length === 0 || resolutions.size === 0)
|
|
311
|
-
return;
|
|
308
|
+
return rewrittenPaths;
|
|
312
309
|
const serverDir = join(standaloneNextDir, "server");
|
|
313
310
|
if (!existsSync(serverDir))
|
|
314
|
-
return;
|
|
311
|
+
return rewrittenPaths;
|
|
315
312
|
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
316
313
|
const pattern = new RegExp(`(["'])((?:` + aliases.map((a) => escape(a.alias)).join("|") + `)(?:/[^"']+)?)\\1`, "g");
|
|
317
|
-
let rewritten = 0;
|
|
318
314
|
for (const f of walkDir(serverDir)) {
|
|
319
315
|
if (!f.absolutePath.endsWith(".js"))
|
|
320
316
|
continue;
|
|
@@ -332,12 +328,13 @@ function rewriteTurbopackAliases(standaloneNextDir, aliases, resolutions) {
|
|
|
332
328
|
});
|
|
333
329
|
if (next !== content) {
|
|
334
330
|
writeFileSync(f.absolutePath, next);
|
|
335
|
-
|
|
331
|
+
rewrittenPaths.push(`.next/server/${f.relativePath.replace(/\\/g, "/")}`);
|
|
336
332
|
}
|
|
337
333
|
}
|
|
338
|
-
if (
|
|
339
|
-
console.log(`next-bun-compile: Rewrote turbopack-mangled aliases in ${
|
|
334
|
+
if (rewrittenPaths.length > 0) {
|
|
335
|
+
console.log(`next-bun-compile: Rewrote turbopack-mangled aliases in ${rewrittenPaths.length} server chunks`);
|
|
340
336
|
}
|
|
337
|
+
return rewrittenPaths;
|
|
341
338
|
}
|
|
342
339
|
function findServerDir(standaloneDir) {
|
|
343
340
|
if (existsSync(join(standaloneDir, "server.js"))) {
|
|
@@ -474,7 +471,12 @@ function fixModuleResolution(standaloneDir) {
|
|
|
474
471
|
const indexPath = join(dir, "index.js");
|
|
475
472
|
if (!existsSync(pkgJsonPath) || existsSync(indexPath))
|
|
476
473
|
continue;
|
|
477
|
-
|
|
474
|
+
let pkg;
|
|
475
|
+
try {
|
|
476
|
+
pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
477
|
+
} catch {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
478
480
|
if (pkg.main && pkg.main !== "index.js") {
|
|
479
481
|
writeFileSync(indexPath, `module.exports = require("./${pkg.main}");`);
|
|
480
482
|
}
|
|
@@ -530,7 +532,7 @@ function generateEntryPoint(options) {
|
|
|
530
532
|
continue;
|
|
531
533
|
const dest = join(externalDir, mod);
|
|
532
534
|
mkdirSync(join(dest, ".."), { recursive: true });
|
|
533
|
-
|
|
535
|
+
copyFileSync(src, dest);
|
|
534
536
|
runtimeFiles.push({
|
|
535
537
|
absolutePath: dest,
|
|
536
538
|
relativePath: `__external/${mod}`,
|
|
@@ -542,13 +544,20 @@ function generateEntryPoint(options) {
|
|
|
542
544
|
}
|
|
543
545
|
const canonicalResolutions = buildCanonicalResolutions(externalDir, turbopackAliases);
|
|
544
546
|
validateAliasResolutions(turbopackAliases, canonicalResolutions);
|
|
545
|
-
rewriteTurbopackAliases(standaloneNextDir, turbopackAliases, canonicalResolutions);
|
|
547
|
+
const rewrittenChunks = rewriteTurbopackAliases(standaloneNextDir, turbopackAliases, canonicalResolutions);
|
|
546
548
|
const assetPrefix = readAssetPrefix(distDir);
|
|
547
549
|
const assetsToEmbed = assetPrefix ? [...publicFiles, ...runtimeFiles] : [...staticFiles, ...publicFiles, ...runtimeFiles];
|
|
548
550
|
if (assetPrefix) {
|
|
549
551
|
console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
|
|
550
552
|
}
|
|
551
553
|
console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${staticFiles.length} static + ${publicFiles.length} public + ${runtimeFiles.length} runtime)`);
|
|
554
|
+
const hasher = createHash("sha256");
|
|
555
|
+
for (const asset of assetsToEmbed) {
|
|
556
|
+
hasher.update(asset.urlPath);
|
|
557
|
+
hasher.update("\x00");
|
|
558
|
+
hasher.update(readFileSync(asset.absolutePath));
|
|
559
|
+
}
|
|
560
|
+
const buildHash = hasher.digest("hex");
|
|
552
561
|
const imports = [];
|
|
553
562
|
const mapEntries = [];
|
|
554
563
|
for (const asset of assetsToEmbed) {
|
|
@@ -755,45 +764,59 @@ process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig);
|
|
|
755
764
|
const currentPort = parseInt(process.env.PORT, 10) || 3000;
|
|
756
765
|
const hostname = process.env.HOSTNAME || "0.0.0.0";
|
|
757
766
|
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
|
|
758
|
-
if (
|
|
767
|
+
if (!Number.isFinite(keepAliveTimeout) || keepAliveTimeout < 0) {
|
|
759
768
|
keepAliveTimeout = undefined;
|
|
760
769
|
}
|
|
761
770
|
|
|
762
771
|
const extractions = ${JSON.stringify(assetExtractions)};
|
|
772
|
+
// Chunks that got __NBC_BASE__ placeholders injected at build time by
|
|
773
|
+
// rewriteTurbopackAliases — only these need text substitution on extract;
|
|
774
|
+
// everything else streams straight from the binary.
|
|
775
|
+
const rewrittenChunks = new Set(${JSON.stringify(rewrittenChunks)});
|
|
776
|
+
// Written to the manifest after a complete extraction. Includes baseDir:
|
|
777
|
+
// if the deploy directory moves, the substituted absolute paths in the
|
|
778
|
+
// rewritten chunks are wrong and everything must be re-extracted.
|
|
779
|
+
const buildStamp = ${JSON.stringify(buildHash)} + "\\n" + baseDir;
|
|
780
|
+
const manifestPath = path.join(baseDir, ".next", ".nbc-extracted");
|
|
763
781
|
async function extractAssets() {
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
for (const [urlPath, diskPath] of extractions) {
|
|
770
|
-
const fullPath = path.join(baseDir, diskPath);
|
|
771
|
-
if (fs.existsSync(fullPath)) continue;
|
|
772
|
-
const embedded = assetMap.get(urlPath);
|
|
773
|
-
if (!embedded) continue;
|
|
774
|
-
todo.push({ diskPath, fullPath, embedded });
|
|
775
|
-
}
|
|
776
|
-
if (todo.length === 0) return;
|
|
782
|
+
// Fast path: a previous boot of this exact build in this exact directory
|
|
783
|
+
// finished extracting — one file read, no per-asset stats.
|
|
784
|
+
try {
|
|
785
|
+
if (fs.readFileSync(manifestPath, "utf-8") === buildStamp) return;
|
|
786
|
+
} catch {}
|
|
777
787
|
|
|
788
|
+
// Full extraction, overwriting whatever is on disk. Skipping existing
|
|
789
|
+
// files would let stale ones (crashed half-extraction, previous build,
|
|
790
|
+
// pre-placed tampering) shadow the embedded assets forever.
|
|
778
791
|
const dirs = new Set();
|
|
779
|
-
for (const
|
|
792
|
+
for (const [, diskPath] of extractions) {
|
|
793
|
+
dirs.add(path.dirname(path.join(baseDir, diskPath)));
|
|
794
|
+
}
|
|
780
795
|
for (const d of dirs) fs.mkdirSync(d, { recursive: true });
|
|
781
796
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const
|
|
789
|
-
if (
|
|
797
|
+
// Concurrent writes, bounded so thousands of in-flight fds can't trip
|
|
798
|
+
// EMFILE under conservative ulimits.
|
|
799
|
+
let idx = 0;
|
|
800
|
+
async function worker() {
|
|
801
|
+
while (idx < extractions.length) {
|
|
802
|
+
const [urlPath, diskPath] = extractions[idx++];
|
|
803
|
+
const embedded = assetMap.get(urlPath);
|
|
804
|
+
if (!embedded) continue;
|
|
805
|
+
const fullPath = path.join(baseDir, diskPath);
|
|
806
|
+
if (rewrittenChunks.has(diskPath)) {
|
|
807
|
+
const text = await Bun.file(embedded).text();
|
|
790
808
|
await Bun.write(fullPath, text.split("__NBC_BASE__").join(baseDir));
|
|
791
|
-
|
|
809
|
+
} else {
|
|
810
|
+
await Bun.write(fullPath, Bun.file(embedded));
|
|
792
811
|
}
|
|
793
812
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
813
|
+
}
|
|
814
|
+
await Promise.all(
|
|
815
|
+
Array.from({ length: Math.min(64, extractions.length) }, worker)
|
|
816
|
+
);
|
|
817
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
818
|
+
fs.writeFileSync(manifestPath, buildStamp);
|
|
819
|
+
console.log(\`Extracted \${extractions.length} assets\`);
|
|
797
820
|
}
|
|
798
821
|
|
|
799
822
|
extractAssets().then(() => {
|
|
@@ -834,7 +857,15 @@ function compile(options) {
|
|
|
834
857
|
...extraArgs
|
|
835
858
|
];
|
|
836
859
|
console.log(`next-bun-compile: Compiling to ${outfile}...`);
|
|
837
|
-
|
|
860
|
+
try {
|
|
861
|
+
execFileSync("bun", args, { stdio: "inherit" });
|
|
862
|
+
} catch (err) {
|
|
863
|
+
if (err.code === "ENOENT") {
|
|
864
|
+
console.error("next-bun-compile: `bun` was not found on PATH. Install it from https://bun.sh and re-run.");
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
throw err;
|
|
868
|
+
}
|
|
838
869
|
console.log(`next-bun-compile: Done → ${outfile}`);
|
|
839
870
|
}
|
|
840
871
|
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-bun-compile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Compile your Next.js app into a Bun single-file executable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"next": "16.1.6",
|
|
29
|
+
"@types/bun": "^1",
|
|
29
30
|
"@types/node": "^20",
|
|
30
31
|
"typescript": "^5"
|
|
31
32
|
},
|
|
@@ -34,8 +35,7 @@
|
|
|
34
35
|
"bun",
|
|
35
36
|
"compile",
|
|
36
37
|
"standalone",
|
|
37
|
-
"executable"
|
|
38
|
-
"adapter"
|
|
38
|
+
"executable"
|
|
39
39
|
],
|
|
40
40
|
"license": "MIT",
|
|
41
41
|
"homepage": "https://github.com/ramonmalcolm10/next-bun-compile#readme",
|