/ next.config.ts
next.config.ts
 1  import type { NextConfig } from "next";
 2  import { execSync } from "child_process";
 3  import { existsSync } from "fs";
 4  import { networkInterfaces } from "os";
 5  import path from "path";
 6  import { fileURLToPath } from "url";
 7  
 8  const PROJECT_ROOT = path.dirname(fileURLToPath(import.meta.url))
 9  const RUNTIME_STATE_GLOBS = [
10    'data/**/*',
11    '.tmp-swarmclaw-build/**/*',
12  ]
13  
14  function getGitSha(): string {
15    try {
16      if (!existsSync(path.join(PROJECT_ROOT, '.git'))) return 'unknown'
17      return execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim()
18    } catch {
19      return 'unknown'
20    }
21  }
22  
23  function getAllowedDevOrigins(): string[] {
24    const allowed = new Set<string>([
25      'localhost',
26      '127.0.0.1',
27      '0.0.0.0',
28    ])
29  
30    // Include all active local IPv4 interfaces so LAN devices can load /_next assets in dev.
31    for (const interfaces of Object.values(networkInterfaces())) {
32      for (const iface of interfaces ?? []) {
33        if ((iface.family === 'IPv4' || (iface.family as string | number) === 4) && !iface.internal) {
34          allowed.add(iface.address)
35        }
36      }
37    }
38  
39    // Optional override for custom origins/hosts, e.g. `NEXT_ALLOWED_DEV_ORIGINS=host1,host2`.
40    const extra = (process.env.NEXT_ALLOWED_DEV_ORIGINS ?? '')
41      .split(',')
42      .map((v) => v.trim())
43      .filter(Boolean)
44      .map((v) => v.replace(/^https?:\/\//, '').replace(/\/$/, ''))
45    for (const host of extra) allowed.add(host)
46  
47    return [...allowed]
48  }
49  
50  const nextConfig: NextConfig = {
51    output: 'standalone',
52    outputFileTracingExcludes: {
53      '/*': RUNTIME_STATE_GLOBS,
54      '/api/**': RUNTIME_STATE_GLOBS,
55      instrumentation: RUNTIME_STATE_GLOBS,
56      '/instrumentation': RUNTIME_STATE_GLOBS,
57      'next-server': RUNTIME_STATE_GLOBS,
58    },
59    turbopack: {
60      // Pin workspace root to the project directory so a stale lockfile
61      // in a parent folder (e.g. ~/) or a nested launch cwd doesn't confuse
62      // native module resolution.
63      root: PROJECT_ROOT,
64    },
65    experimental: {
66      // Limit build workers to 1 inside Docker to avoid SQLITE_BUSY contention
67      // when multiple workers collect page data concurrently.
68      ...(process.env.SWARMCLAW_BUILD_MODE ? { cpus: 1 } : {}),
69    },
70    env: {
71      NEXT_PUBLIC_GIT_SHA: getGitSha(),
72      NEXT_PUBLIC_WS_PORT: String((Number(process.env.PORT) || 3456) + 1),
73    },
74    // Allow external network access
75    serverExternalPackages: [
76      'ws',
77      'highlight.js', 'better-sqlite3',
78      'discord.js', '@discordjs/ws', '@discordjs/rest',
79      'grammy',
80      '@slack/bolt', '@slack/web-api', '@slack/socket-mode',
81      '@whiskeysockets/baileys',
82      'qrcode',
83      'just-bash',
84    ],
85    allowedDevOrigins: getAllowedDevOrigins(),
86  };
87  
88  export default nextConfig;