Files
open-design/apps/web/next.config.ts
PerishFireandGitHub c6d11018a0 Refresh desktop integration control plane (#123)
* feat(dev): add desktop tools-dev control plane

* refactor(sidecar): split Open Design contracts

Move Open Design-specific sidecar protocol definitions into @open-design/contracts so sidecar and platform can remain descriptor-driven primitives.

* refactor(daemon): organize package sources

Keep daemon app code, tests, and sidecar entrypoints in separate package directories so each layer can be built and verified independently.

* chore(repo): streamline maintenance entrypoints

Centralize agent guidance by directory and reduce root command chains while preserving the existing build scope.

* docs: translate agent guidance to English

* fix(sidecar): tolerate stale IPC sockets

Remove stale Unix socket files only after confirming no listener is active, so tools-dev can restart after unclean shutdowns.
2026-04-30 14:23:53 +08:00

74 lines
3.0 KiB
TypeScript

import type { NextConfig } from 'next';
import { dirname, isAbsolute, relative } from 'node:path';
import { fileURLToPath } from 'node:url';
// Daemon port the local Express server binds to (see apps/daemon/src/cli.ts). The
// dev-all launcher overrides OD_PORT after probing for a free port; we read
// the same env so /api, /artifacts, and /frames always reach the right
// daemon instance during `next dev`.
const DAEMON_PORT = Number(process.env.OD_PORT) || 7456;
const DAEMON_ORIGIN = `http://127.0.0.1:${DAEMON_PORT}`;
// We ship as a static export so the existing `od` daemon can keep serving a
// single-process production build (out/ replaces the old dist/). Project IDs
// are unbounded user input, so we route everything through a single optional
// catch-all client page (`app/[[...slug]]/page.tsx`) that reads the URL at
// runtime — Next.js generates one shell HTML, the daemon falls back to it
// for any non-API request, and the existing client router renders the right
// view.
const isProd = process.env.NODE_ENV !== 'development';
const WEB_ROOT = dirname(fileURLToPath(import.meta.url));
function resolveDevDistDir() {
const configured = process.env.OD_WEB_DIST_DIR;
if (!configured) return '.next';
return isAbsolute(configured) ? relative(WEB_ROOT, configured) || '.' : configured;
}
const DEV_DIST_DIR = resolveDevDistDir();
function resolveDevTsconfigPath() {
const configured = process.env.OD_WEB_TSCONFIG_PATH;
if (!configured) return undefined;
return isAbsolute(configured) ? relative(WEB_ROOT, configured) || 'tsconfig.json' : configured;
}
const DEV_TSCONFIG_PATH = resolveDevTsconfigPath();
const nextConfig: NextConfig = {
allowedDevOrigins: ['127.0.0.1'],
reactStrictMode: true,
...(DEV_TSCONFIG_PATH ? { typescript: { tsconfigPath: DEV_TSCONFIG_PATH } } : {}),
// Keep the bundle output predictable so the daemon's STATIC_DIR can point
// at it without any glob trickery.
distDir: isProd ? 'out' : DEV_DIST_DIR,
...(isProd
? {
output: 'export' as const,
// `next export` skips trailing slashes by default; opting in keeps
// the daemon's static fallback simple (every directory has its own
// index.html on disk).
trailingSlash: true,
images: { unoptimized: true },
}
: {
async rewrites() {
// In dev we run the daemon on a sibling port; proxy the app API
// proxy so the SPA can hit /api, /artifacts, and /frames without
// CORS gymnastics. SSE on /api/chat works through this rewrite
// because Next.js's dev server streams responses unbuffered.
return [
{ source: '/api/:path*', destination: `${DAEMON_ORIGIN}/api/:path*` },
{ source: '/artifacts/:path*', destination: `${DAEMON_ORIGIN}/artifacts/:path*` },
{ source: '/frames/:path*', destination: `${DAEMON_ORIGIN}/frames/:path*` },
];
},
devIndicators: {
position: 'bottom-right',
},
}),
};
export default nextConfig;