import type { NextConfig } from "next"; import createNextIntlPlugin from "next-intl/plugin"; import { execSync } from "child_process"; import { readFileSync } from "fs"; import { join } from "path"; // Prefer an explicit build arg (passed in by CI / Docker, where .git is // excluded from the build context) and fall back to `git rev-parse` for // local builds. let gitCommitHash = process.env.GIT_COMMIT?.trim() || ""; if (!gitCommitHash) { try { gitCommitHash = execSync("git rev-parse --short HEAD").toString().trim(); } catch { gitCommitHash = "unknown"; } } // Normalise full 40-char SHAs (e.g. ${{ github.sha }}) to the short form. if (/^[0-9a-f]{40}$/i.test(gitCommitHash)) { gitCommitHash = gitCommitHash.slice(0, 7); } let appVersion = "0.0.0"; try { appVersion = readFileSync(join(import.meta.dirname, "VERSION"), "utf-8").trim(); } catch { // VERSION file not found } // Subpath deployment, e.g. NEXT_PUBLIC_BASE_PATH=/webmail. Read at build time // because Next.js bakes basePath into emitted asset URLs and route metadata. // Trailing slash is stripped; an empty/missing value disables the feature. const rawBasePath = process.env.NEXT_PUBLIC_BASE_PATH?.trim() ?? ""; const basePath = rawBasePath.replace(/\/+$/, ""); if (basePath && !basePath.startsWith("/")) { throw new Error( `NEXT_PUBLIC_BASE_PATH must start with "/" (got: ${JSON.stringify(rawBasePath)})` ); } const nextConfig: NextConfig = { output: "standalone", // 127.0.0.1 alongside the existing LAN entry: electron/main.ts always // loads its window at 127.0.0.1 (see ELECTRON_LOAD_URL and // startStandaloneServer()), so dev-mode Electron runs (only used by // integration/tests/11-electron-notification.spec.ts today) need it in // this allowlist the same way any other cross-origin dev client would. allowedDevOrigins: ["192.168.1.51", "127.0.0.1"], basePath: basePath || undefined, // esbuild ships native binaries + a README the bundler can't parse; load // it from node_modules at runtime instead of trying to bundle it. Used by // PLUGIN_DEV_DIR's on-the-fly bundler. // // @signalapp/sqlcipher is a native N-API addon resolved at runtime by // node-gyp-build (a directory scan of prebuilds/), which a bundler cannot // follow. It is also an OPTIONAL dependency - absent on musl/Alpine, where // both Dockerfiles build - so it must never be a hard build-time import. // lib/mail-index/binding.ts guards the require; this keeps webpack from // trying to resolve it at all. serverExternalPackages: ["esbuild", "@signalapp/sqlcipher"], // Sibling repos checked out under ./repos/ are unrelated source trees that // Turbopack's NFT can otherwise rope into the trace when dynamic fs calls // confuse it. Keeps the build from ballooning memory tracing dead code. outputFileTracingExcludes: { "*": ["./repos/**/*"], }, turbopack: { root: import.meta.dirname, }, env: { NEXT_PUBLIC_GIT_COMMIT: gitCommitHash, NEXT_PUBLIC_APP_VERSION: appVersion, NEXT_PUBLIC_BASE_PATH: basePath, NEXT_PUBLIC_DEV_MOCK_JMAP: process.env.DEV_MOCK_JMAP ?? "", }, }; const withNextIntl = createNextIntlPlugin(); export default withNextIntl(nextConfig);