298 lines
9.1 KiB
TypeScript
Executable File
298 lines
9.1 KiB
TypeScript
Executable File
import { cpSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
import { join, resolve } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { defineConfig, type ResolvedConfig, type ViteDevServer } from 'vite';
|
|
import react from '@vitejs/plugin-react';
|
|
import { VitePWA } from 'vite-plugin-pwa';
|
|
|
|
const DEV_APP_UPDATE_PATH = '/__app-update';
|
|
const processEnv =
|
|
(
|
|
globalThis as typeof globalThis & {
|
|
process?: {
|
|
env?: Record<string, string | undefined>;
|
|
};
|
|
}
|
|
).process?.env ?? {};
|
|
const VITE_PUBLIC_HMR_HOST = processEnv.VITE_PUBLIC_HMR_HOST ?? 'test.sm-home.cloud';
|
|
const VITE_PUBLIC_HMR_PROTOCOL = processEnv.VITE_PUBLIC_HMR_PROTOCOL ?? 'wss';
|
|
const VITE_PUBLIC_HMR_CLIENT_PORT = Number(processEnv.VITE_PUBLIC_HMR_CLIENT_PORT ?? 443);
|
|
const VITE_EMPTY_OUT_DIR = processEnv.VITE_EMPTY_OUT_DIR !== 'false';
|
|
const VITE_FILTER_PUBLIC_DIR = processEnv.VITE_FILTER_PUBLIC_DIR === 'true';
|
|
const VITE_DISABLE_MODULE_PRELOAD = processEnv.VITE_DISABLE_MODULE_PRELOAD === 'true';
|
|
const VITE_DISABLE_PWA = processEnv.VITE_DISABLE_PWA === 'true';
|
|
const WORK_SERVER_HTTP_TARGET = processEnv.WORK_SERVER_URL?.trim() || 'http://work-server:3100';
|
|
const WORK_SERVER_WS_TARGET = (() => {
|
|
try {
|
|
const parsed = new URL(WORK_SERVER_HTTP_TARGET);
|
|
parsed.protocol = parsed.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
return parsed.toString();
|
|
} catch {
|
|
return WORK_SERVER_HTTP_TARGET.replace(/^http/i, 'ws');
|
|
}
|
|
})();
|
|
const ROOT_DIR = fileURLToPath(new URL('.', import.meta.url));
|
|
|
|
function shouldIgnoreDevUpdatePath(watchedPath: string) {
|
|
return (
|
|
watchedPath.includes('/.auto_codex/') ||
|
|
watchedPath.includes('/dist/') ||
|
|
watchedPath.includes('/app-dist/') ||
|
|
watchedPath.includes('/test-app-dist/') ||
|
|
watchedPath.includes('/coverage/') ||
|
|
watchedPath.includes('/node_modules/') ||
|
|
watchedPath.includes('/.docker/') ||
|
|
watchedPath.includes('/.npm/') ||
|
|
watchedPath.includes('/_cacache/') ||
|
|
watchedPath.includes('/.tmp/') ||
|
|
watchedPath.includes('/.codex/') ||
|
|
watchedPath.includes('/vendor_imports/') ||
|
|
watchedPath.includes('/etc/servers/') ||
|
|
watchedPath.includes('/public/.codex_chat/') ||
|
|
watchedPath.endsWith('/.server-command-runner-heartbeat.json')
|
|
);
|
|
}
|
|
|
|
function createDevAppUpdatePlugin() {
|
|
let updateToken = `${Date.now()}`;
|
|
|
|
const bumpUpdateToken = () => {
|
|
updateToken = `${Date.now()}`;
|
|
};
|
|
|
|
const shouldTrackPath = (watchedPath: string) => !shouldIgnoreDevUpdatePath(watchedPath);
|
|
|
|
return {
|
|
name: 'dev-app-update-probe',
|
|
apply: 'serve' as const,
|
|
configureServer(server: ViteDevServer) {
|
|
const handleFileChange = (file: string) => {
|
|
if (shouldTrackPath(file)) {
|
|
bumpUpdateToken();
|
|
}
|
|
};
|
|
|
|
server.watcher.on('add', handleFileChange);
|
|
server.watcher.on('change', handleFileChange);
|
|
server.watcher.on('unlink', handleFileChange);
|
|
|
|
server.middlewares.use(DEV_APP_UPDATE_PATH, (_request, response) => {
|
|
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
response.setHeader('Cache-Control', 'no-store');
|
|
response.end(
|
|
JSON.stringify({
|
|
token: updateToken,
|
|
updatedAt: new Date(Number(updateToken)).toISOString(),
|
|
}),
|
|
);
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
function createDevServiceWorkerPlugin() {
|
|
return {
|
|
name: 'dev-service-worker-entry',
|
|
apply: 'serve' as const,
|
|
configureServer(server: ViteDevServer) {
|
|
server.middlewares.use((request, response, next) => {
|
|
const requestUrl = request.url?.trim() ?? '';
|
|
const requestPath = requestUrl.split('?', 1)[0];
|
|
|
|
if (requestPath !== '/sw.js' && requestPath !== '/dev-sw.js') {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
void server
|
|
.transformRequest('/src/sw.js')
|
|
.then((result) => {
|
|
if (!result?.code) {
|
|
response.statusCode = 404;
|
|
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
response.end('Not Found');
|
|
return;
|
|
}
|
|
|
|
response.statusCode = 200;
|
|
response.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
response.setHeader('Cache-Control', 'no-store');
|
|
response.setHeader('Service-Worker-Allowed', '/');
|
|
response.end(result.code);
|
|
})
|
|
.catch(next);
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
function copyPublicAssetsExceptCodexChat() {
|
|
let resolvedConfig: ResolvedConfig | null = null;
|
|
|
|
const copyDirectory = (sourceDir: string, targetDir: string) => {
|
|
if (!existsSync(sourceDir)) {
|
|
return;
|
|
}
|
|
|
|
mkdirSync(targetDir, { recursive: true });
|
|
|
|
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
if (entry.name === '.codex_chat') {
|
|
continue;
|
|
}
|
|
|
|
const sourcePath = join(sourceDir, entry.name);
|
|
const targetPath = join(targetDir, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
copyDirectory(sourcePath, targetPath);
|
|
continue;
|
|
}
|
|
|
|
if (entry.isFile()) {
|
|
mkdirSync(join(targetPath, '..'), { recursive: true });
|
|
cpSync(sourcePath, targetPath, { force: true });
|
|
}
|
|
}
|
|
};
|
|
|
|
return {
|
|
name: 'copy-public-assets-except-codex-chat',
|
|
apply: 'build' as const,
|
|
configResolved(config: ResolvedConfig) {
|
|
resolvedConfig = config;
|
|
},
|
|
closeBundle() {
|
|
if (!VITE_FILTER_PUBLIC_DIR || !resolvedConfig) {
|
|
return;
|
|
}
|
|
|
|
const publicDir = join(resolvedConfig.root, 'public');
|
|
const outDir = resolvedConfig.build.outDir.startsWith('/')
|
|
? resolvedConfig.build.outDir
|
|
: join(resolvedConfig.root, resolvedConfig.build.outDir);
|
|
|
|
copyDirectory(publicDir, outDir);
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig({
|
|
resolve: {
|
|
extensions: ['.mjs', '.ts', '.tsx', '.js', '.jsx', '.mts', '.json'],
|
|
alias: VITE_DISABLE_PWA
|
|
? {
|
|
'virtual:pwa-register': resolve(ROOT_DIR, 'src/app/main/pwaRegisterStub.ts'),
|
|
}
|
|
: undefined,
|
|
},
|
|
build: {
|
|
copyPublicDir: !VITE_FILTER_PUBLIC_DIR,
|
|
emptyOutDir: VITE_EMPTY_OUT_DIR,
|
|
modulePreload: VITE_DISABLE_MODULE_PRELOAD ? false : undefined,
|
|
},
|
|
server: {
|
|
host: '0.0.0.0',
|
|
port: 5173,
|
|
hmr: {
|
|
host: VITE_PUBLIC_HMR_HOST,
|
|
protocol: VITE_PUBLIC_HMR_PROTOCOL as 'ws' | 'wss',
|
|
clientPort: VITE_PUBLIC_HMR_CLIENT_PORT,
|
|
},
|
|
allowedHosts: ['sm-home.cloud', 'test.sm-home.cloud', 'preview.sm-home.cloud', 'rel.sm-home.cloud'],
|
|
watch: {
|
|
ignored: (watchedPath) => shouldIgnoreDevUpdatePath(watchedPath),
|
|
},
|
|
proxy: {
|
|
'/api': {
|
|
target: WORK_SERVER_HTTP_TARGET,
|
|
changeOrigin: true,
|
|
},
|
|
'/ws/chat': {
|
|
target: WORK_SERVER_WS_TARGET,
|
|
ws: true,
|
|
changeOrigin: true,
|
|
},
|
|
'/.codex_chat': {
|
|
target: WORK_SERVER_HTTP_TARGET,
|
|
changeOrigin: true,
|
|
},
|
|
'/public/.codex_chat': {
|
|
target: WORK_SERVER_HTTP_TARGET,
|
|
changeOrigin: true,
|
|
},
|
|
},
|
|
},
|
|
preview: {
|
|
host: '0.0.0.0',
|
|
port: 5173,
|
|
allowedHosts: ['sm-home.cloud', 'test.sm-home.cloud', 'preview.sm-home.cloud', 'rel.sm-home.cloud'],
|
|
},
|
|
plugins: [
|
|
react(),
|
|
createDevAppUpdatePlugin(),
|
|
createDevServiceWorkerPlugin(),
|
|
copyPublicAssetsExceptCodexChat(),
|
|
!VITE_DISABLE_PWA &&
|
|
VitePWA({
|
|
injectRegister: null,
|
|
strategies: 'injectManifest',
|
|
srcDir: 'src',
|
|
filename: 'sw.js',
|
|
registerType: 'prompt',
|
|
includeAssets: ['favicon.svg', 'apple-touch-icon.svg'],
|
|
workbox: {
|
|
globPatterns: ['**/*.{js,css,html,ico,svg,woff2,webmanifest}'],
|
|
maximumFileSizeToCacheInBytes: 8 * 1024 * 1024,
|
|
},
|
|
injectManifest: {
|
|
globIgnores: ['**/.codex_chat/**', '**/*.png', '**/*.webp'],
|
|
maximumFileSizeToCacheInBytes: 8 * 1024 * 1024,
|
|
},
|
|
manifest: {
|
|
id: '/',
|
|
name: 'AI Code App',
|
|
short_name: 'AI Code App',
|
|
description: 'Ant Design 기반 UI 샘플과 문서를 확인하는 AI Code App',
|
|
theme_color: '#165dff',
|
|
background_color: '#eff5ff',
|
|
display: 'standalone',
|
|
lang: 'ko',
|
|
scope: '/',
|
|
start_url: '/',
|
|
icons: [
|
|
{
|
|
src: '/pwa-192x192.svg',
|
|
sizes: '192x192',
|
|
type: 'image/svg+xml',
|
|
},
|
|
{
|
|
src: '/pwa-512x512.svg',
|
|
sizes: '512x512',
|
|
type: 'image/svg+xml',
|
|
purpose: 'any maskable',
|
|
},
|
|
],
|
|
shortcuts: [
|
|
{
|
|
name: 'E-Reader',
|
|
short_name: 'E-Reader',
|
|
description: '인터넷 기사와 웹 콘텐츠를 전자책처럼 읽습니다.',
|
|
url: '/play/apps?app=e-reader',
|
|
icons: [
|
|
{
|
|
src: '/pwa-192x192.svg',
|
|
sizes: '192x192',
|
|
type: 'image/svg+xml',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
devOptions: {
|
|
enabled: false,
|
|
},
|
|
}),
|
|
].filter(Boolean),
|
|
});
|