import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import { spawnSync } from 'node:child_process' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const desktopRoot = path.resolve(__dirname, '..') const workspaceRoot = path.resolve(desktopRoot, '..') const backendProject = path.join( workspaceRoot, 'YD_TrainingCompetition_Api', 'YD_TrainingCompetition_Api.csproj', ) const frontendRoot = path.join(workspaceRoot, 'YD_TrainingCompetition_Api_Web') const frontendDist = path.join(frontendRoot, 'dist') const packagedBackend = path.join(desktopRoot, 'build', 'backend') const packagedFrontend = path.join(desktopRoot, 'frontend') const skipFrontendBuild = process.argv.includes('--skip-frontend-build') function run(command, args, options = {}) { const useCmd = process.platform === 'win32' && command === 'npm' const executable = useCmd ? process.env.ComSpec || 'cmd.exe' : command const executableArgs = useCmd ? ['/d', '/s', '/c', `npm ${args.join(' ')}`] : args console.log(`\n> ${command} ${args.join(' ')}`) const result = spawnSync(executable, executableArgs, { cwd: options.cwd || workspaceRoot, env: { ...process.env, ...(options.env || {}), }, stdio: 'inherit', }) if (result.error) { throw result.error } if (result.status !== 0) { process.exit(result.status || 1) } } function cleanDirectory(directory) { rmSync(directory, { force: true, recursive: true }) mkdirSync(directory, { recursive: true }) } function copyDirectory(source, destination) { mkdirSync(destination, { recursive: true }) for (const entry of readdirSync(source, { withFileTypes: true })) { const sourcePath = path.join(source, entry.name) const destinationPath = path.join(destination, entry.name) if (entry.isDirectory()) { copyDirectory(sourcePath, destinationPath) } else if (entry.isFile()) { copyFileSync(sourcePath, destinationPath) } } } cleanDirectory(packagedBackend) cleanDirectory(packagedFrontend) run('dotnet', [ 'publish', backendProject, '-c', 'Release', '-r', 'win-x64', '--self-contained', 'true', '-o', packagedBackend, '/p:PublishSingleFile=false', ]) if (skipFrontendBuild) { console.log('\n> skip frontend install/build, using existing dist') } else { run('npm', ['install'], { cwd: frontendRoot }) run('npm', ['run', 'build'], { cwd: frontendRoot, env: { VITE_API_BASE_URL: 'http://localhost:9996', }, }) } if (!existsSync(frontendDist)) { throw new Error(`前端构建产物不存在:${frontendDist}`) } copyDirectory(frontendDist, packagedFrontend) // 同时把前端拷进后端 wwwroot,使后端通过 http 直接托管页面与静态资源。 const backendWwwRoot = path.join(packagedBackend, 'wwwroot') cleanDirectory(backendWwwRoot) copyDirectory(frontendDist, backendWwwRoot) const appSettingsPath = path.join(workspaceRoot, 'YD_TrainingCompetition_Api', 'appsettings.json') if (existsSync(appSettingsPath)) { copyFileSync(appSettingsPath, path.join(packagedBackend, 'appsettings.json')) } console.log('\n桌面端打包资源已准备完成。')