next-bun-compile 0.4.1 → 0.5.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 CHANGED
@@ -32,63 +32,129 @@ function toVarName(filePath) {
32
32
  const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
33
33
  return `asset_${safe}_${hash}`;
34
34
  }
35
+ function findPackageDirs(nodeModulesDir, pkg) {
36
+ const dirs = [];
37
+ const direct = join(nodeModulesDir, pkg);
38
+ if (existsSync(direct))
39
+ dirs.push(direct);
40
+ const bunDir = join(nodeModulesDir, ".bun");
41
+ if (existsSync(bunDir)) {
42
+ const scope = pkg.startsWith("@") ? pkg.split("/")[0] + "+" + pkg.split("/")[1] : pkg;
43
+ for (const entry of readdirSync(bunDir)) {
44
+ if (!entry.startsWith(scope + "@"))
45
+ continue;
46
+ const hoisted = join(bunDir, entry, "node_modules", pkg);
47
+ if (existsSync(hoisted))
48
+ dirs.push(hoisted);
49
+ }
50
+ }
51
+ return dirs;
52
+ }
35
53
  function generateStubs(standaloneDir) {
36
54
  const stubs = [
37
55
  {
38
- path: "node_modules/next/dist/server/dev/next-dev-server.js",
56
+ pkg: "next",
57
+ subpath: "dist/server/dev/next-dev-server.js",
39
58
  content: "module.exports = { default: null };"
40
59
  },
41
60
  {
42
- path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
61
+ pkg: "next",
62
+ subpath: "dist/server/lib/router-utils/setup-dev-bundler.js",
43
63
  content: "module.exports = {};"
44
64
  },
45
65
  {
46
- path: "node_modules/@opentelemetry/api/index.js",
66
+ pkg: "@opentelemetry/api",
67
+ subpath: "index.js",
47
68
  content: "throw new Error('not installed');"
48
69
  },
49
70
  {
50
- path: "node_modules/critters/index.js",
71
+ pkg: "critters",
72
+ subpath: "index.js",
51
73
  content: "module.exports = {};"
52
74
  }
53
75
  ];
76
+ const nodeModulesDir = join(standaloneDir, "node_modules");
54
77
  let count = 0;
55
78
  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 });
79
+ const pkgDirs = findPackageDirs(nodeModulesDir, stub.pkg);
80
+ if (pkgDirs.length === 0)
81
+ pkgDirs.push(join(nodeModulesDir, stub.pkg));
82
+ for (const pkgDir of pkgDirs) {
83
+ const fullPath = join(pkgDir, stub.subpath);
84
+ if (!existsSync(fullPath)) {
85
+ const dir = join(fullPath, "..");
86
+ if (!existsSync(dir)) {
87
+ mkdirSync(dir, { recursive: true });
88
+ }
89
+ writeFileSync(fullPath, stub.content);
90
+ count++;
61
91
  }
62
- writeFileSync(fullPath, stub.content);
63
- count++;
64
92
  }
65
93
  }
66
94
  if (count > 0) {
67
95
  console.log(`next-bun-compile: Created ${count} module stubs`);
68
96
  }
69
97
  }
98
+ function findServerDir(standaloneDir) {
99
+ if (existsSync(join(standaloneDir, "server.js"))) {
100
+ return standaloneDir;
101
+ }
102
+ function search(dir) {
103
+ if (!existsSync(dir))
104
+ return null;
105
+ for (const entry of readdirSync(dir)) {
106
+ if (entry === "node_modules")
107
+ continue;
108
+ const full = join(dir, entry);
109
+ if (!statSync(full).isDirectory())
110
+ continue;
111
+ if (existsSync(join(full, "server.js")))
112
+ return full;
113
+ const found2 = search(full);
114
+ if (found2)
115
+ return found2;
116
+ }
117
+ return null;
118
+ }
119
+ const found = search(standaloneDir);
120
+ if (!found) {
121
+ throw new Error("next-bun-compile: Could not find server.js in standalone output");
122
+ }
123
+ const rel = relative(standaloneDir, found);
124
+ console.log(`next-bun-compile: Monorepo layout detected — server.js found at ${rel}/`);
125
+ return found;
126
+ }
70
127
  function patchRequireHook(standaloneDir) {
71
- const hookPath = join(standaloneDir, "node_modules/next/dist/server/require-hook.js");
72
- if (!existsSync(hookPath))
73
- return;
74
- let content = readFileSync(hookPath, "utf-8");
128
+ const nodeModulesDir = join(standaloneDir, "node_modules");
129
+ const nextDirs = findPackageDirs(nodeModulesDir, "next");
75
130
  const target = "let resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;";
76
- if (!content.includes(target))
77
- return;
78
- content = content.replace(target, `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
79
- let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`);
80
- writeFileSync(hookPath, content);
81
- console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
131
+ const replacement = `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
132
+ let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`;
133
+ let patched = 0;
134
+ for (const nextDir of nextDirs) {
135
+ const hookPath = join(nextDir, "dist/server/require-hook.js");
136
+ if (!existsSync(hookPath))
137
+ continue;
138
+ let content = readFileSync(hookPath, "utf-8");
139
+ if (!content.includes(target))
140
+ continue;
141
+ content = content.replace(target, replacement);
142
+ writeFileSync(hookPath, content);
143
+ patched++;
144
+ }
145
+ if (patched > 0) {
146
+ console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
147
+ }
82
148
  }
83
- function collectExternalModules(standaloneDir) {
84
- const chunksDir = join(standaloneDir, ".next/server/chunks/ssr");
149
+ function collectExternalModules(standaloneDir, serverDir) {
150
+ const chunksDir = join(serverDir, ".next/server/chunks");
85
151
  if (!existsSync(chunksDir))
86
152
  return [];
87
153
  const seeds = new Set;
88
- for (const entry of readdirSync(chunksDir)) {
89
- if (!entry.endsWith(".js"))
154
+ for (const { absolutePath } of walkDir(chunksDir)) {
155
+ if (!absolutePath.endsWith(".js"))
90
156
  continue;
91
- const content = readFileSync(join(chunksDir, entry), "utf-8");
157
+ const content = readFileSync(absolutePath, "utf-8");
92
158
  for (const match of content.matchAll(/require\("(next\/dist\/[^"]+)"\)/g)) {
93
159
  seeds.add(match[1]);
94
160
  }
@@ -132,6 +198,7 @@ function collectExternalModules(standaloneDir) {
132
198
  }
133
199
  function generateEntryPoint(options) {
134
200
  const { standaloneDir, distDir, projectDir } = options;
201
+ const serverDir = findServerDir(standaloneDir);
135
202
  generateStubs(standaloneDir);
136
203
  patchRequireHook(standaloneDir);
137
204
  const staticDir = join(distDir, "static");
@@ -144,14 +211,14 @@ function generateEntryPoint(options) {
144
211
  ...f,
145
212
  urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
146
213
  }));
147
- const standaloneNextDir = join(standaloneDir, ".next");
214
+ const standaloneNextDir = join(serverDir, ".next");
148
215
  const runtimeFiles = walkDir(standaloneNextDir).map((f) => ({
149
216
  ...f,
150
217
  urlPath: `__runtime/.next/${f.relativePath.replace(/\\/g, "/")}`
151
218
  }));
152
- const externalModules = collectExternalModules(standaloneDir);
219
+ const externalModules = collectExternalModules(standaloneDir, serverDir);
153
220
  const externalPaths = ["next/package.json", ...externalModules];
154
- const externalDir = join(standaloneDir, ".next/__external");
221
+ const externalDir = join(serverDir, ".next/__external");
155
222
  for (const mod of externalPaths) {
156
223
  const src = join(standaloneDir, "node_modules", mod);
157
224
  if (!existsSync(src))
@@ -179,18 +246,18 @@ function generateEntryPoint(options) {
179
246
  const mapEntries = [];
180
247
  for (const asset of assetsToEmbed) {
181
248
  const varName = toVarName(asset.urlPath);
182
- const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
249
+ const importPath = relative(serverDir, asset.absolutePath).replace(/\\/g, "/");
183
250
  imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
184
251
  mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
185
252
  }
186
- writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
253
+ writeFileSync(join(serverDir, "assets.generated.js"), `${imports.join(`
187
254
  `)}
188
255
  export const assetMap = new Map([
189
256
  ${mapEntries.join(`
190
257
  `)}
191
258
  ]);
192
259
  `);
193
- const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
260
+ const standaloneServerSrc = readFileSync(join(serverDir, "server.js"), "utf-8");
194
261
  const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
195
262
  if (!configMatch) {
196
263
  throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
@@ -246,15 +313,16 @@ extractAssets().then(() => {
246
313
  });
247
314
  }).catch((err) => { console.error(err); process.exit(1); });
248
315
  `;
249
- writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
316
+ writeFileSync(join(serverDir, "server-entry.js"), serverEntry);
317
+ return serverDir;
250
318
  }
251
319
 
252
320
  // src/compile.ts
253
321
  import { execFileSync } from "node:child_process";
254
322
  import { join as join2 } from "node:path";
255
323
  function compile(options) {
256
- const { standaloneDir, outfile, extraArgs = [] } = options;
257
- const entryPoint = join2(standaloneDir, "server-entry.js");
324
+ const { serverDir, outfile, extraArgs = [] } = options;
325
+ const entryPoint = join2(serverDir, "server-entry.js");
258
326
  const args = [
259
327
  "build",
260
328
  entryPoint,
@@ -293,5 +361,5 @@ var ctxPath = join3(distDir, "bun-compile-ctx.json");
293
361
  if (existsSync2(ctxPath)) {
294
362
  console.log("next-bun-compile: Using build context from adapter");
295
363
  }
296
- generateEntryPoint({ standaloneDir, distDir, projectDir });
297
- compile({ standaloneDir, outfile: join3(projectDir, "server"), extraArgs });
364
+ var serverDir = generateEntryPoint({ standaloneDir, distDir, projectDir });
365
+ compile({ serverDir, outfile: join3(projectDir, "server"), extraArgs });
package/dist/compile.js CHANGED
@@ -5,8 +5,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
5
  import { execFileSync } from "node:child_process";
6
6
  import { join } from "node:path";
7
7
  function compile(options) {
8
- const { standaloneDir, outfile, extraArgs = [] } = options;
9
- const entryPoint = join(standaloneDir, "server-entry.js");
8
+ const { serverDir, outfile, extraArgs = [] } = options;
9
+ const entryPoint = join(serverDir, "server-entry.js");
10
10
  const args = [
11
11
  "build",
12
12
  entryPoint,
package/dist/generate.js CHANGED
@@ -31,63 +31,129 @@ function toVarName(filePath) {
31
31
  const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
32
32
  return `asset_${safe}_${hash}`;
33
33
  }
34
+ function findPackageDirs(nodeModulesDir, pkg) {
35
+ const dirs = [];
36
+ const direct = join(nodeModulesDir, pkg);
37
+ if (existsSync(direct))
38
+ dirs.push(direct);
39
+ const bunDir = join(nodeModulesDir, ".bun");
40
+ if (existsSync(bunDir)) {
41
+ const scope = pkg.startsWith("@") ? pkg.split("/")[0] + "+" + pkg.split("/")[1] : pkg;
42
+ for (const entry of readdirSync(bunDir)) {
43
+ if (!entry.startsWith(scope + "@"))
44
+ continue;
45
+ const hoisted = join(bunDir, entry, "node_modules", pkg);
46
+ if (existsSync(hoisted))
47
+ dirs.push(hoisted);
48
+ }
49
+ }
50
+ return dirs;
51
+ }
34
52
  function generateStubs(standaloneDir) {
35
53
  const stubs = [
36
54
  {
37
- path: "node_modules/next/dist/server/dev/next-dev-server.js",
55
+ pkg: "next",
56
+ subpath: "dist/server/dev/next-dev-server.js",
38
57
  content: "module.exports = { default: null };"
39
58
  },
40
59
  {
41
- path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
60
+ pkg: "next",
61
+ subpath: "dist/server/lib/router-utils/setup-dev-bundler.js",
42
62
  content: "module.exports = {};"
43
63
  },
44
64
  {
45
- path: "node_modules/@opentelemetry/api/index.js",
65
+ pkg: "@opentelemetry/api",
66
+ subpath: "index.js",
46
67
  content: "throw new Error('not installed');"
47
68
  },
48
69
  {
49
- path: "node_modules/critters/index.js",
70
+ pkg: "critters",
71
+ subpath: "index.js",
50
72
  content: "module.exports = {};"
51
73
  }
52
74
  ];
75
+ const nodeModulesDir = join(standaloneDir, "node_modules");
53
76
  let count = 0;
54
77
  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 });
78
+ const pkgDirs = findPackageDirs(nodeModulesDir, stub.pkg);
79
+ if (pkgDirs.length === 0)
80
+ pkgDirs.push(join(nodeModulesDir, stub.pkg));
81
+ for (const pkgDir of pkgDirs) {
82
+ const fullPath = join(pkgDir, stub.subpath);
83
+ if (!existsSync(fullPath)) {
84
+ const dir = join(fullPath, "..");
85
+ if (!existsSync(dir)) {
86
+ mkdirSync(dir, { recursive: true });
87
+ }
88
+ writeFileSync(fullPath, stub.content);
89
+ count++;
60
90
  }
61
- writeFileSync(fullPath, stub.content);
62
- count++;
63
91
  }
64
92
  }
65
93
  if (count > 0) {
66
94
  console.log(`next-bun-compile: Created ${count} module stubs`);
67
95
  }
68
96
  }
97
+ function findServerDir(standaloneDir) {
98
+ if (existsSync(join(standaloneDir, "server.js"))) {
99
+ return standaloneDir;
100
+ }
101
+ function search(dir) {
102
+ if (!existsSync(dir))
103
+ return null;
104
+ for (const entry of readdirSync(dir)) {
105
+ if (entry === "node_modules")
106
+ continue;
107
+ const full = join(dir, entry);
108
+ if (!statSync(full).isDirectory())
109
+ continue;
110
+ if (existsSync(join(full, "server.js")))
111
+ return full;
112
+ const found2 = search(full);
113
+ if (found2)
114
+ return found2;
115
+ }
116
+ return null;
117
+ }
118
+ const found = search(standaloneDir);
119
+ if (!found) {
120
+ throw new Error("next-bun-compile: Could not find server.js in standalone output");
121
+ }
122
+ const rel = relative(standaloneDir, found);
123
+ console.log(`next-bun-compile: Monorepo layout detected — server.js found at ${rel}/`);
124
+ return found;
125
+ }
69
126
  function patchRequireHook(standaloneDir) {
70
- const hookPath = join(standaloneDir, "node_modules/next/dist/server/require-hook.js");
71
- if (!existsSync(hookPath))
72
- return;
73
- let content = readFileSync(hookPath, "utf-8");
127
+ const nodeModulesDir = join(standaloneDir, "node_modules");
128
+ const nextDirs = findPackageDirs(nodeModulesDir, "next");
74
129
  const target = "let resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;";
75
- if (!content.includes(target))
76
- return;
77
- content = content.replace(target, `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
78
- let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`);
79
- writeFileSync(hookPath, content);
80
- console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
130
+ const replacement = `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
131
+ let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`;
132
+ let patched = 0;
133
+ for (const nextDir of nextDirs) {
134
+ const hookPath = join(nextDir, "dist/server/require-hook.js");
135
+ if (!existsSync(hookPath))
136
+ continue;
137
+ let content = readFileSync(hookPath, "utf-8");
138
+ if (!content.includes(target))
139
+ continue;
140
+ content = content.replace(target, replacement);
141
+ writeFileSync(hookPath, content);
142
+ patched++;
143
+ }
144
+ if (patched > 0) {
145
+ console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
146
+ }
81
147
  }
82
- function collectExternalModules(standaloneDir) {
83
- const chunksDir = join(standaloneDir, ".next/server/chunks/ssr");
148
+ function collectExternalModules(standaloneDir, serverDir) {
149
+ const chunksDir = join(serverDir, ".next/server/chunks");
84
150
  if (!existsSync(chunksDir))
85
151
  return [];
86
152
  const seeds = new Set;
87
- for (const entry of readdirSync(chunksDir)) {
88
- if (!entry.endsWith(".js"))
153
+ for (const { absolutePath } of walkDir(chunksDir)) {
154
+ if (!absolutePath.endsWith(".js"))
89
155
  continue;
90
- const content = readFileSync(join(chunksDir, entry), "utf-8");
156
+ const content = readFileSync(absolutePath, "utf-8");
91
157
  for (const match of content.matchAll(/require\("(next\/dist\/[^"]+)"\)/g)) {
92
158
  seeds.add(match[1]);
93
159
  }
@@ -131,6 +197,7 @@ function collectExternalModules(standaloneDir) {
131
197
  }
132
198
  function generateEntryPoint(options) {
133
199
  const { standaloneDir, distDir, projectDir } = options;
200
+ const serverDir = findServerDir(standaloneDir);
134
201
  generateStubs(standaloneDir);
135
202
  patchRequireHook(standaloneDir);
136
203
  const staticDir = join(distDir, "static");
@@ -143,14 +210,14 @@ function generateEntryPoint(options) {
143
210
  ...f,
144
211
  urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
145
212
  }));
146
- const standaloneNextDir = join(standaloneDir, ".next");
213
+ const standaloneNextDir = join(serverDir, ".next");
147
214
  const runtimeFiles = walkDir(standaloneNextDir).map((f) => ({
148
215
  ...f,
149
216
  urlPath: `__runtime/.next/${f.relativePath.replace(/\\/g, "/")}`
150
217
  }));
151
- const externalModules = collectExternalModules(standaloneDir);
218
+ const externalModules = collectExternalModules(standaloneDir, serverDir);
152
219
  const externalPaths = ["next/package.json", ...externalModules];
153
- const externalDir = join(standaloneDir, ".next/__external");
220
+ const externalDir = join(serverDir, ".next/__external");
154
221
  for (const mod of externalPaths) {
155
222
  const src = join(standaloneDir, "node_modules", mod);
156
223
  if (!existsSync(src))
@@ -178,18 +245,18 @@ function generateEntryPoint(options) {
178
245
  const mapEntries = [];
179
246
  for (const asset of assetsToEmbed) {
180
247
  const varName = toVarName(asset.urlPath);
181
- const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
248
+ const importPath = relative(serverDir, asset.absolutePath).replace(/\\/g, "/");
182
249
  imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
183
250
  mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
184
251
  }
185
- writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
252
+ writeFileSync(join(serverDir, "assets.generated.js"), `${imports.join(`
186
253
  `)}
187
254
  export const assetMap = new Map([
188
255
  ${mapEntries.join(`
189
256
  `)}
190
257
  ]);
191
258
  `);
192
- const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
259
+ const standaloneServerSrc = readFileSync(join(serverDir, "server.js"), "utf-8");
193
260
  const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
194
261
  if (!configMatch) {
195
262
  throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
@@ -245,7 +312,8 @@ extractAssets().then(() => {
245
312
  });
246
313
  }).catch((err) => { console.error(err); process.exit(1); });
247
314
  `;
248
- writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
315
+ writeFileSync(join(serverDir, "server-entry.js"), serverEntry);
316
+ return serverDir;
249
317
  }
250
318
  export {
251
319
  generateEntryPoint
package/dist/index.js CHANGED
@@ -31,63 +31,129 @@ function toVarName(filePath) {
31
31
  const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
32
32
  return `asset_${safe}_${hash}`;
33
33
  }
34
+ function findPackageDirs(nodeModulesDir, pkg) {
35
+ const dirs = [];
36
+ const direct = join(nodeModulesDir, pkg);
37
+ if (existsSync(direct))
38
+ dirs.push(direct);
39
+ const bunDir = join(nodeModulesDir, ".bun");
40
+ if (existsSync(bunDir)) {
41
+ const scope = pkg.startsWith("@") ? pkg.split("/")[0] + "+" + pkg.split("/")[1] : pkg;
42
+ for (const entry of readdirSync(bunDir)) {
43
+ if (!entry.startsWith(scope + "@"))
44
+ continue;
45
+ const hoisted = join(bunDir, entry, "node_modules", pkg);
46
+ if (existsSync(hoisted))
47
+ dirs.push(hoisted);
48
+ }
49
+ }
50
+ return dirs;
51
+ }
34
52
  function generateStubs(standaloneDir) {
35
53
  const stubs = [
36
54
  {
37
- path: "node_modules/next/dist/server/dev/next-dev-server.js",
55
+ pkg: "next",
56
+ subpath: "dist/server/dev/next-dev-server.js",
38
57
  content: "module.exports = { default: null };"
39
58
  },
40
59
  {
41
- path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
60
+ pkg: "next",
61
+ subpath: "dist/server/lib/router-utils/setup-dev-bundler.js",
42
62
  content: "module.exports = {};"
43
63
  },
44
64
  {
45
- path: "node_modules/@opentelemetry/api/index.js",
65
+ pkg: "@opentelemetry/api",
66
+ subpath: "index.js",
46
67
  content: "throw new Error('not installed');"
47
68
  },
48
69
  {
49
- path: "node_modules/critters/index.js",
70
+ pkg: "critters",
71
+ subpath: "index.js",
50
72
  content: "module.exports = {};"
51
73
  }
52
74
  ];
75
+ const nodeModulesDir = join(standaloneDir, "node_modules");
53
76
  let count = 0;
54
77
  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 });
78
+ const pkgDirs = findPackageDirs(nodeModulesDir, stub.pkg);
79
+ if (pkgDirs.length === 0)
80
+ pkgDirs.push(join(nodeModulesDir, stub.pkg));
81
+ for (const pkgDir of pkgDirs) {
82
+ const fullPath = join(pkgDir, stub.subpath);
83
+ if (!existsSync(fullPath)) {
84
+ const dir = join(fullPath, "..");
85
+ if (!existsSync(dir)) {
86
+ mkdirSync(dir, { recursive: true });
87
+ }
88
+ writeFileSync(fullPath, stub.content);
89
+ count++;
60
90
  }
61
- writeFileSync(fullPath, stub.content);
62
- count++;
63
91
  }
64
92
  }
65
93
  if (count > 0) {
66
94
  console.log(`next-bun-compile: Created ${count} module stubs`);
67
95
  }
68
96
  }
97
+ function findServerDir(standaloneDir) {
98
+ if (existsSync(join(standaloneDir, "server.js"))) {
99
+ return standaloneDir;
100
+ }
101
+ function search(dir) {
102
+ if (!existsSync(dir))
103
+ return null;
104
+ for (const entry of readdirSync(dir)) {
105
+ if (entry === "node_modules")
106
+ continue;
107
+ const full = join(dir, entry);
108
+ if (!statSync(full).isDirectory())
109
+ continue;
110
+ if (existsSync(join(full, "server.js")))
111
+ return full;
112
+ const found2 = search(full);
113
+ if (found2)
114
+ return found2;
115
+ }
116
+ return null;
117
+ }
118
+ const found = search(standaloneDir);
119
+ if (!found) {
120
+ throw new Error("next-bun-compile: Could not find server.js in standalone output");
121
+ }
122
+ const rel = relative(standaloneDir, found);
123
+ console.log(`next-bun-compile: Monorepo layout detected — server.js found at ${rel}/`);
124
+ return found;
125
+ }
69
126
  function patchRequireHook(standaloneDir) {
70
- const hookPath = join(standaloneDir, "node_modules/next/dist/server/require-hook.js");
71
- if (!existsSync(hookPath))
72
- return;
73
- let content = readFileSync(hookPath, "utf-8");
127
+ const nodeModulesDir = join(standaloneDir, "node_modules");
128
+ const nextDirs = findPackageDirs(nodeModulesDir, "next");
74
129
  const target = "let resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;";
75
- if (!content.includes(target))
76
- return;
77
- content = content.replace(target, `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
78
- let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`);
79
- writeFileSync(hookPath, content);
80
- console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
130
+ const replacement = `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
131
+ let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`;
132
+ let patched = 0;
133
+ for (const nextDir of nextDirs) {
134
+ const hookPath = join(nextDir, "dist/server/require-hook.js");
135
+ if (!existsSync(hookPath))
136
+ continue;
137
+ let content = readFileSync(hookPath, "utf-8");
138
+ if (!content.includes(target))
139
+ continue;
140
+ content = content.replace(target, replacement);
141
+ writeFileSync(hookPath, content);
142
+ patched++;
143
+ }
144
+ if (patched > 0) {
145
+ console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
146
+ }
81
147
  }
82
- function collectExternalModules(standaloneDir) {
83
- const chunksDir = join(standaloneDir, ".next/server/chunks/ssr");
148
+ function collectExternalModules(standaloneDir, serverDir) {
149
+ const chunksDir = join(serverDir, ".next/server/chunks");
84
150
  if (!existsSync(chunksDir))
85
151
  return [];
86
152
  const seeds = new Set;
87
- for (const entry of readdirSync(chunksDir)) {
88
- if (!entry.endsWith(".js"))
153
+ for (const { absolutePath } of walkDir(chunksDir)) {
154
+ if (!absolutePath.endsWith(".js"))
89
155
  continue;
90
- const content = readFileSync(join(chunksDir, entry), "utf-8");
156
+ const content = readFileSync(absolutePath, "utf-8");
91
157
  for (const match of content.matchAll(/require\("(next\/dist\/[^"]+)"\)/g)) {
92
158
  seeds.add(match[1]);
93
159
  }
@@ -131,6 +197,7 @@ function collectExternalModules(standaloneDir) {
131
197
  }
132
198
  function generateEntryPoint(options) {
133
199
  const { standaloneDir, distDir, projectDir } = options;
200
+ const serverDir = findServerDir(standaloneDir);
134
201
  generateStubs(standaloneDir);
135
202
  patchRequireHook(standaloneDir);
136
203
  const staticDir = join(distDir, "static");
@@ -143,14 +210,14 @@ function generateEntryPoint(options) {
143
210
  ...f,
144
211
  urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
145
212
  }));
146
- const standaloneNextDir = join(standaloneDir, ".next");
213
+ const standaloneNextDir = join(serverDir, ".next");
147
214
  const runtimeFiles = walkDir(standaloneNextDir).map((f) => ({
148
215
  ...f,
149
216
  urlPath: `__runtime/.next/${f.relativePath.replace(/\\/g, "/")}`
150
217
  }));
151
- const externalModules = collectExternalModules(standaloneDir);
218
+ const externalModules = collectExternalModules(standaloneDir, serverDir);
152
219
  const externalPaths = ["next/package.json", ...externalModules];
153
- const externalDir = join(standaloneDir, ".next/__external");
220
+ const externalDir = join(serverDir, ".next/__external");
154
221
  for (const mod of externalPaths) {
155
222
  const src = join(standaloneDir, "node_modules", mod);
156
223
  if (!existsSync(src))
@@ -178,18 +245,18 @@ function generateEntryPoint(options) {
178
245
  const mapEntries = [];
179
246
  for (const asset of assetsToEmbed) {
180
247
  const varName = toVarName(asset.urlPath);
181
- const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
248
+ const importPath = relative(serverDir, asset.absolutePath).replace(/\\/g, "/");
182
249
  imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
183
250
  mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
184
251
  }
185
- writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
252
+ writeFileSync(join(serverDir, "assets.generated.js"), `${imports.join(`
186
253
  `)}
187
254
  export const assetMap = new Map([
188
255
  ${mapEntries.join(`
189
256
  `)}
190
257
  ]);
191
258
  `);
192
- const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
259
+ const standaloneServerSrc = readFileSync(join(serverDir, "server.js"), "utf-8");
193
260
  const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
194
261
  if (!configMatch) {
195
262
  throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
@@ -245,15 +312,16 @@ extractAssets().then(() => {
245
312
  });
246
313
  }).catch((err) => { console.error(err); process.exit(1); });
247
314
  `;
248
- writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
315
+ writeFileSync(join(serverDir, "server-entry.js"), serverEntry);
316
+ return serverDir;
249
317
  }
250
318
 
251
319
  // src/compile.ts
252
320
  import { execFileSync } from "node:child_process";
253
321
  import { join as join2 } from "node:path";
254
322
  function compile(options) {
255
- const { standaloneDir, outfile, extraArgs = [] } = options;
256
- const entryPoint = join2(standaloneDir, "server-entry.js");
323
+ const { serverDir, outfile, extraArgs = [] } = options;
324
+ const entryPoint = join2(serverDir, "server-entry.js");
257
325
  const args = [
258
326
  "build",
259
327
  entryPoint,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-bun-compile",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Next.js Build Adapter that compiles your app into a Bun single-file executable",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",