mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-10 16:51:11 +08:00
【新增】私有证书
This commit is contained in:
264
frontend/apps/node-spa-preview/src/utils/cli-config.js
Normal file
264
frontend/apps/node-spa-preview/src/utils/cli-config.js
Normal file
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* SPA 预览服务器的命令行配置工具
|
||||
* 用于交互式管理环境变量
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import readline from 'node:readline';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
DEFAULT_CONFIG,
|
||||
parseConfig,
|
||||
validateConfig,
|
||||
generateEnvFile,
|
||||
readEnvFile,
|
||||
getConfigSummary
|
||||
} from './config.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
/**
|
||||
* 创建 readline 接口
|
||||
*/
|
||||
function createInterface() {
|
||||
return readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 询问用户输入
|
||||
* @param {string} question - 问题
|
||||
* @param {string} defaultValue - 默认值
|
||||
* @returns {Promise<string>} 用户输入
|
||||
*/
|
||||
function askQuestion(question, defaultValue = '') {
|
||||
return new Promise((resolve) => {
|
||||
const rl = createInterface();
|
||||
const prompt = defaultValue
|
||||
? `${question} ${chalk.gray(`(default: ${defaultValue})`)}: `
|
||||
: `${question}: `;
|
||||
|
||||
rl.question(prompt, (answer) => {
|
||||
rl.close();
|
||||
resolve(answer.trim() || defaultValue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 询问是/否问题
|
||||
* @param {string} question - 问题
|
||||
* @param {boolean} defaultValue - 默认值
|
||||
* @returns {Promise<boolean>} 用户选择
|
||||
*/
|
||||
function askYesNo(question, defaultValue = true) {
|
||||
return new Promise((resolve) => {
|
||||
const rl = createInterface();
|
||||
const defaultText = defaultValue ? 'Y/n' : 'y/N';
|
||||
const prompt = `${question} ${chalk.gray(`(${defaultText})`)}: `;
|
||||
|
||||
rl.question(prompt, (answer) => {
|
||||
rl.close();
|
||||
const normalized = answer.trim().toLowerCase();
|
||||
if (normalized === '') {
|
||||
resolve(defaultValue);
|
||||
} else {
|
||||
resolve(normalized === 'y' || normalized === 'yes');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示配置摘要
|
||||
* @param {object} config - 配置对象
|
||||
*/
|
||||
function displayConfigSummary(config) {
|
||||
const summary = getConfigSummary(config);
|
||||
|
||||
console.log(chalk.cyan('\n📋 配置摘要:'));
|
||||
console.log(chalk.cyan('─'.repeat(50)));
|
||||
|
||||
console.log(chalk.yellow('🖥️ Server:'));
|
||||
console.log(` URL: ${chalk.green(summary.server.url)}`);
|
||||
console.log(` Public Directory: ${chalk.green(summary.server.publicDir)}`);
|
||||
console.log(` Development Mode: ${summary.server.devMode ? chalk.green('Enabled') : chalk.red('Disabled')}`);
|
||||
|
||||
console.log(chalk.yellow('\n🔄 API Proxy:'));
|
||||
if (summary.proxy.enabled) {
|
||||
console.log(` Status: ${chalk.green('Enabled')}`);
|
||||
console.log(` Target: ${chalk.green(summary.proxy.target)}`);
|
||||
console.log(` Prefix: ${chalk.green(summary.proxy.prefix)}`);
|
||||
} else {
|
||||
console.log(` Status: ${chalk.red('Disabled')}`);
|
||||
}
|
||||
|
||||
console.log(chalk.yellow('\n🛣️ SPA Routing:'));
|
||||
console.log(` Fallback: ${summary.spa.fallbackEnabled ? chalk.green('Enabled') : chalk.red('Disabled')}`);
|
||||
console.log(` Excluded Extensions: ${chalk.green(summary.spa.excludeExtensions)} types`);
|
||||
|
||||
console.log(chalk.yellow('\n🌐 CORS:'));
|
||||
console.log(` Status: ${summary.cors.enabled ? chalk.green('Enabled') : chalk.red('Disabled')}`);
|
||||
console.log(` Origin: ${chalk.green(summary.cors.origin)}`);
|
||||
|
||||
console.log(chalk.cyan('─'.repeat(50)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 交互式配置向导
|
||||
* @returns {Promise<object>} 配置对象
|
||||
*/
|
||||
async function configWizard() {
|
||||
console.log(chalk.blue('🚀 SPA 预览服务器配置向导'));
|
||||
console.log(chalk.gray('按 Enter 键使用默认值\n'));
|
||||
|
||||
const config = {};
|
||||
|
||||
// 服务器配置
|
||||
console.log(chalk.yellow('📡 服务器配置:'));
|
||||
config.PORT = await askQuestion('端口', DEFAULT_CONFIG.PORT.toString());
|
||||
config.HOST = await askQuestion('主机地址', DEFAULT_CONFIG.HOST);
|
||||
|
||||
// 静态文件配置
|
||||
console.log(chalk.yellow('\n📁 静态文件配置:'));
|
||||
config.PUBLIC_DIR = await askQuestion('公共目录', DEFAULT_CONFIG.PUBLIC_DIR);
|
||||
config.FALLBACK_FILE = await askQuestion('回退文件', DEFAULT_CONFIG.FALLBACK_FILE);
|
||||
|
||||
// API 代理配置
|
||||
console.log(chalk.yellow('\n🔄 API 代理配置:'));
|
||||
const enableProxy = await askYesNo('启用 API 代理?', false);
|
||||
if (enableProxy) {
|
||||
config.API_TARGET = await askQuestion('API 目标 URL (例如: http://localhost:3000)');
|
||||
config.API_PREFIX = await askQuestion('API 前缀', DEFAULT_CONFIG.API_PREFIX);
|
||||
} else {
|
||||
config.API_TARGET = '';
|
||||
config.API_PREFIX = DEFAULT_CONFIG.API_PREFIX;
|
||||
}
|
||||
|
||||
// 开发配置
|
||||
console.log(chalk.yellow('\n🔧 开发配置:'));
|
||||
config.DEV_MODE = await askYesNo('启用开发模式?', DEFAULT_CONFIG.DEV_MODE);
|
||||
const logLevels = ['debug', 'info', 'warn', 'error'];
|
||||
console.log(`可用的日志级别: ${logLevels.join(', ')}`);
|
||||
config.LOG_LEVEL = await askQuestion('日志级别', DEFAULT_CONFIG.LOG_LEVEL);
|
||||
|
||||
// CORS 配置
|
||||
console.log(chalk.yellow('\n🌐 CORS 配置:'));
|
||||
config.CORS_ENABLED = await askYesNo('启用 CORS?', DEFAULT_CONFIG.CORS_ENABLED);
|
||||
if (config.CORS_ENABLED) {
|
||||
config.CORS_ORIGIN = await askQuestion('CORS 源地址 (* 表示所有,或逗号分隔的 URL)', DEFAULT_CONFIG.CORS_ORIGIN);
|
||||
} else {
|
||||
config.CORS_ORIGIN = DEFAULT_CONFIG.CORS_ORIGIN;
|
||||
}
|
||||
|
||||
// SPA 路由配置
|
||||
console.log(chalk.yellow('\n🛣️ SPA 路由配置:'));
|
||||
config.SPA_FALLBACK_ENABLED = await askYesNo('启用 SPA 回退?', DEFAULT_CONFIG.SPA_FALLBACK_ENABLED);
|
||||
if (config.SPA_FALLBACK_ENABLED) {
|
||||
config.SPA_EXCLUDE_EXTENSIONS = await askQuestion(
|
||||
'排除的文件扩展名 (逗号分隔)',
|
||||
DEFAULT_CONFIG.SPA_EXCLUDE_EXTENSIONS
|
||||
);
|
||||
} else {
|
||||
config.SPA_EXCLUDE_EXTENSIONS = DEFAULT_CONFIG.SPA_EXCLUDE_EXTENSIONS;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
async function main() {
|
||||
const envPath = path.resolve(process.cwd(), '.env');
|
||||
const envExamplePath = path.resolve(process.cwd(), '.env.example');
|
||||
|
||||
try {
|
||||
console.log(chalk.blue('🔧 SPA 预览服务器配置工具\n'));
|
||||
|
||||
// 检查 .env 文件是否存在
|
||||
const envExists = fs.existsSync(envPath);
|
||||
if (envExists) {
|
||||
console.log(chalk.green('✅ 发现现有的 .env 文件'));
|
||||
const useExisting = await askYesNo('加载现有配置?', true);
|
||||
|
||||
if (useExisting) {
|
||||
const existingEnv = readEnvFile(envPath);
|
||||
const existingConfig = parseConfig(existingEnv);
|
||||
|
||||
console.log(chalk.blue('\n📖 当前配置:'));
|
||||
displayConfigSummary(existingConfig);
|
||||
|
||||
const modify = await askYesNo('\n修改配置?', false);
|
||||
if (!modify) {
|
||||
console.log(chalk.green('\n✅ 配置未更改'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行配置向导
|
||||
const config = await configWizard();
|
||||
|
||||
// 验证配置
|
||||
const validation = validateConfig(config);
|
||||
if (!validation.isValid) {
|
||||
console.log(chalk.red('\n❌ 配置验证失败:'));
|
||||
validation.errors.forEach(error => {
|
||||
console.log(chalk.red(` • ${error}`));
|
||||
});
|
||||
|
||||
const continueAnyway = await askYesNo('\n仍然保存配置?', false);
|
||||
if (!continueAnyway) {
|
||||
console.log(chalk.yellow('\n⚠️ 配置已取消'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示摘要
|
||||
displayConfigSummary(config);
|
||||
|
||||
// 确认保存
|
||||
const confirmSave = await askYesNo('\n保存此配置?', true);
|
||||
if (!confirmSave) {
|
||||
console.log(chalk.yellow('\n⚠️ 配置已取消'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
const envContent = generateEnvFile(config);
|
||||
fs.writeFileSync(envPath, envContent, 'utf8');
|
||||
|
||||
console.log(chalk.green(`\n✅ 配置已保存到 ${envPath}`));
|
||||
|
||||
// 如果不存在则创建 .env.example
|
||||
if (!fs.existsSync(envExamplePath)) {
|
||||
const exampleConfig = { ...DEFAULT_CONFIG };
|
||||
const exampleContent = generateEnvFile(exampleConfig);
|
||||
fs.writeFileSync(envExamplePath, exampleContent, 'utf8');
|
||||
console.log(chalk.green(`✅ 示例配置已保存到 ${envExamplePath}`));
|
||||
}
|
||||
|
||||
console.log(chalk.blue('\n🚀 现在可以使用以下命令启动服务器:'));
|
||||
console.log(chalk.cyan(' node src/server.js'));
|
||||
console.log(chalk.cyan(' # 或者'));
|
||||
console.log(chalk.cyan(' pnpm dev'));
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red('\n❌ 错误:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接调用则运行
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
|
||||
export { main as configWizard };
|
||||
246
frontend/apps/node-spa-preview/src/utils/config.js
Normal file
246
frontend/apps/node-spa-preview/src/utils/config.js
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* SPA 预览服务器的配置工具
|
||||
* 用于管理和验证环境变量配置
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
/**
|
||||
* 默认配置值
|
||||
*/
|
||||
export const DEFAULT_CONFIG = {
|
||||
// 服务器配置
|
||||
PORT: 5173,
|
||||
HOST: '0.0.0.0',
|
||||
|
||||
// 静态文件配置
|
||||
PUBLIC_DIR: 'public',
|
||||
FALLBACK_FILE: 'index.html',
|
||||
|
||||
// API 代理配置
|
||||
API_TARGET: '',
|
||||
API_PREFIX: '/api',
|
||||
API_PROXY_MODE: 'prefix', // 'prefix' 或 'include'
|
||||
|
||||
// 开发配置
|
||||
DEV_MODE: true,
|
||||
LOG_LEVEL: 'info',
|
||||
|
||||
// CORS 配置
|
||||
CORS_ENABLED: true,
|
||||
CORS_ORIGIN: '*',
|
||||
|
||||
// SPA 路由配置
|
||||
SPA_FALLBACK_ENABLED: true,
|
||||
SPA_EXCLUDE_EXTENSIONS: '.js,.css,.png,.jpg,.jpeg,.gif,.svg,.ico,.woff,.woff2,.ttf,.eot'
|
||||
};
|
||||
|
||||
/**
|
||||
* 配置验证规则
|
||||
*/
|
||||
export const CONFIG_VALIDATORS = {
|
||||
PORT: (value) => {
|
||||
const port = Number(value);
|
||||
return port > 0 && port <= 65535 ? null : '端口必须在 1 到 65535 之间';
|
||||
},
|
||||
|
||||
HOST: (value) => {
|
||||
const validHosts = ['0.0.0.0', 'localhost', '127.0.0.1'];
|
||||
const isValidIP = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(value);
|
||||
return validHosts.includes(value) || isValidIP ? null : '无效的主机地址';
|
||||
},
|
||||
|
||||
API_TARGET: (value) => {
|
||||
if (!value) return null; // 可选项
|
||||
const urlPattern = /^https?:\/\/.+/;
|
||||
return urlPattern.test(value) ? null : 'API_TARGET 必须是有效的 HTTP/HTTPS URL';
|
||||
},
|
||||
|
||||
API_PROXY_MODE: (value) => {
|
||||
const validModes = ['prefix', 'include'];
|
||||
return validModes.includes(value) ? null : `API_PROXY_MODE 必须是以下值之一: ${validModes.join(', ')}`;
|
||||
},
|
||||
|
||||
LOG_LEVEL: (value) => {
|
||||
const validLevels = ['debug', 'info', 'warn', 'error'];
|
||||
return validLevels.includes(value) ? null : `LOG_LEVEL 必须是以下值之一: ${validLevels.join(', ')}`;
|
||||
},
|
||||
|
||||
CORS_ORIGIN: (value) => {
|
||||
if (value === '*') return null;
|
||||
const origins = value.split(',').map(o => o.trim());
|
||||
const urlPattern = /^https?:\/\/.+/;
|
||||
const invalidOrigins = origins.filter(origin => !urlPattern.test(origin));
|
||||
return invalidOrigins.length === 0 ? null : `无效的 CORS 源: ${invalidOrigins.join(', ')}`;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析环境变量配置
|
||||
* @param {object} env - 环境变量对象
|
||||
* @returns {object} 解析后的配置对象
|
||||
*/
|
||||
export function parseConfig(env = process.env) {
|
||||
const config = {};
|
||||
|
||||
// 解析每个配置值
|
||||
Object.keys(DEFAULT_CONFIG).forEach(key => {
|
||||
const envValue = env[key];
|
||||
const defaultValue = DEFAULT_CONFIG[key];
|
||||
|
||||
if (envValue !== undefined) {
|
||||
// 解析布尔值
|
||||
if (typeof defaultValue === 'boolean') {
|
||||
config[key] = envValue === 'true';
|
||||
}
|
||||
// 解析数字值
|
||||
else if (typeof defaultValue === 'number') {
|
||||
config[key] = Number(envValue) || defaultValue;
|
||||
}
|
||||
// 字符串值
|
||||
else {
|
||||
config[key] = envValue;
|
||||
}
|
||||
} else {
|
||||
config[key] = defaultValue;
|
||||
}
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证配置
|
||||
* @param {object} config - 配置对象
|
||||
* @returns {object} 验证结果 { isValid: boolean, errors: string[] }
|
||||
*/
|
||||
export function validateConfig(config) {
|
||||
const errors = [];
|
||||
|
||||
Object.keys(CONFIG_VALIDATORS).forEach(key => {
|
||||
const validator = CONFIG_VALIDATORS[key];
|
||||
const value = config[key];
|
||||
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
const error = validator(value);
|
||||
if (error) {
|
||||
errors.push(`${key}: ${error}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 .env 文件内容
|
||||
* @param {object} config - 配置对象
|
||||
* @returns {string} .env 文件内容
|
||||
*/
|
||||
export function generateEnvFile(config) {
|
||||
// 与默认值合并以确保所有值都存在
|
||||
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
const lines = [
|
||||
'# SPA 预览服务器配置',
|
||||
'# 生成时间: ' + new Date().toISOString(),
|
||||
'',
|
||||
'# 服务器配置',
|
||||
`PORT=${fullConfig.PORT}`,
|
||||
`HOST=${fullConfig.HOST}`,
|
||||
'',
|
||||
'# 静态文件配置',
|
||||
`PUBLIC_DIR=${fullConfig.PUBLIC_DIR}`,
|
||||
`FALLBACK_FILE=${fullConfig.FALLBACK_FILE}`,
|
||||
'',
|
||||
'# API 代理配置',
|
||||
`API_TARGET=${fullConfig.API_TARGET}`,
|
||||
`API_PREFIX=${fullConfig.API_PREFIX}`,
|
||||
'',
|
||||
'# 开发配置',
|
||||
`DEV_MODE=${fullConfig.DEV_MODE}`,
|
||||
`LOG_LEVEL=${fullConfig.LOG_LEVEL}`,
|
||||
'',
|
||||
'# CORS 配置',
|
||||
`CORS_ENABLED=${fullConfig.CORS_ENABLED}`,
|
||||
`CORS_ORIGIN=${fullConfig.CORS_ORIGIN}`,
|
||||
'',
|
||||
'# SPA 路由配置',
|
||||
`SPA_FALLBACK_ENABLED=${fullConfig.SPA_FALLBACK_ENABLED}`,
|
||||
`SPA_EXCLUDE_EXTENSIONS=${fullConfig.SPA_EXCLUDE_EXTENSIONS}`
|
||||
];
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 .env 文件
|
||||
* @param {string} envPath - .env 文件路径
|
||||
* @returns {object} 环境变量对象
|
||||
*/
|
||||
export function readEnvFile(envPath) {
|
||||
try {
|
||||
const content = fs.readFileSync(envPath, 'utf8');
|
||||
const env = {};
|
||||
|
||||
content.split('\n').forEach(line => {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed && !trimmed.startsWith('#')) {
|
||||
const [key, ...valueParts] = trimmed.split('=');
|
||||
if (key && valueParts.length > 0) {
|
||||
env[key.trim()] = valueParts.join('=').trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return env;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 .env 文件
|
||||
* @param {string} envPath - .env 文件路径
|
||||
* @param {object} config - 配置对象
|
||||
*/
|
||||
export function writeEnvFile(envPath, config) {
|
||||
const content = generateEnvFile(config);
|
||||
fs.writeFileSync(envPath, content, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置摘要信息
|
||||
* @param {object} config - 配置对象
|
||||
* @returns {object} 配置摘要
|
||||
*/
|
||||
export function getConfigSummary(config) {
|
||||
return {
|
||||
server: {
|
||||
url: `http://${config.HOST}:${config.PORT}`,
|
||||
publicDir: config.PUBLIC_DIR,
|
||||
devMode: config.DEV_MODE
|
||||
},
|
||||
proxy: {
|
||||
enabled: !!config.API_TARGET,
|
||||
target: config.API_TARGET,
|
||||
prefix: config.API_PREFIX
|
||||
},
|
||||
spa: {
|
||||
fallbackEnabled: config.SPA_FALLBACK_ENABLED,
|
||||
excludeExtensions: config.SPA_EXCLUDE_EXTENSIONS.split(',').length
|
||||
},
|
||||
cors: {
|
||||
enabled: config.CORS_ENABLED,
|
||||
origin: config.CORS_ORIGIN
|
||||
}
|
||||
};
|
||||
}
|
||||
235
frontend/apps/node-spa-preview/src/utils/test-config.js
Normal file
235
frontend/apps/node-spa-preview/src/utils/test-config.js
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 配置系统测试脚本
|
||||
* 用于验证环境变量配置功能
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
DEFAULT_CONFIG,
|
||||
parseConfig,
|
||||
validateConfig,
|
||||
generateEnvFile,
|
||||
readEnvFile,
|
||||
getConfigSummary
|
||||
} from './config.js';
|
||||
|
||||
/**
|
||||
* 测试配置解析功能
|
||||
*/
|
||||
function testConfigParsing() {
|
||||
console.log(chalk.blue('🧪 测试配置解析功能...'));
|
||||
|
||||
const testEnv = {
|
||||
PORT: '8080',
|
||||
HOST: '127.0.0.1',
|
||||
API_TARGET: 'http://localhost:4000',
|
||||
DEV_MODE: 'false',
|
||||
CORS_ENABLED: 'true',
|
||||
LOG_LEVEL: 'debug'
|
||||
};
|
||||
|
||||
const config = parseConfig(testEnv);
|
||||
|
||||
console.log('✅ 解析的配置:', {
|
||||
port: config.PORT,
|
||||
host: config.HOST,
|
||||
apiTarget: config.API_TARGET,
|
||||
devMode: config.DEV_MODE,
|
||||
corsEnabled: config.CORS_ENABLED,
|
||||
logLevel: config.LOG_LEVEL
|
||||
});
|
||||
|
||||
// 验证类型
|
||||
const typeChecks = [
|
||||
{ key: 'PORT', expected: 'number', actual: typeof config.PORT },
|
||||
{ key: 'DEV_MODE', expected: 'boolean', actual: typeof config.DEV_MODE },
|
||||
{ key: 'CORS_ENABLED', expected: 'boolean', actual: typeof config.CORS_ENABLED }
|
||||
];
|
||||
|
||||
typeChecks.forEach(check => {
|
||||
if (check.expected === check.actual) {
|
||||
console.log(chalk.green(`✅ ${check.key}: ${check.actual}`));
|
||||
} else {
|
||||
console.log(chalk.red(`❌ ${check.key}: expected ${check.expected}, got ${check.actual}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试配置验证功能
|
||||
*/
|
||||
function testConfigValidation() {
|
||||
console.log(chalk.blue('\n🧪 测试配置验证功能...'));
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
name: '有效配置',
|
||||
config: {
|
||||
PORT: 3000,
|
||||
HOST: '0.0.0.0',
|
||||
API_TARGET: 'http://localhost:4000',
|
||||
LOG_LEVEL: 'info',
|
||||
CORS_ORIGIN: '*'
|
||||
},
|
||||
shouldPass: true
|
||||
},
|
||||
{
|
||||
name: '无效端口',
|
||||
config: {
|
||||
PORT: 99999,
|
||||
HOST: '0.0.0.0'
|
||||
},
|
||||
shouldPass: false
|
||||
},
|
||||
{
|
||||
name: '无效 API 目标',
|
||||
config: {
|
||||
API_TARGET: 'not-a-url'
|
||||
},
|
||||
shouldPass: false
|
||||
},
|
||||
{
|
||||
name: '无效日志级别',
|
||||
config: {
|
||||
LOG_LEVEL: 'invalid'
|
||||
},
|
||||
shouldPass: false
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
const validation = validateConfig(testCase.config);
|
||||
const passed = validation.isValid === testCase.shouldPass;
|
||||
|
||||
if (passed) {
|
||||
console.log(chalk.green(`✅ ${testCase.name}`));
|
||||
} else {
|
||||
console.log(chalk.red(`❌ ${testCase.name}`));
|
||||
if (validation.errors.length > 0) {
|
||||
validation.errors.forEach(error => {
|
||||
console.log(chalk.red(` • ${error}`));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 .env 文件生成
|
||||
*/
|
||||
function testEnvFileGeneration() {
|
||||
console.log(chalk.blue('\n🧪 测试 .env 文件生成...'));
|
||||
|
||||
const testConfig = {
|
||||
PORT: 5000,
|
||||
HOST: 'localhost',
|
||||
API_TARGET: 'http://localhost:3000',
|
||||
API_PREFIX: '/api/v1',
|
||||
DEV_MODE: true,
|
||||
LOG_LEVEL: 'debug',
|
||||
CORS_ENABLED: true,
|
||||
CORS_ORIGIN: 'http://localhost:3000,https://example.com',
|
||||
SPA_FALLBACK_ENABLED: true,
|
||||
SPA_EXCLUDE_EXTENSIONS: '.js,.css,.png'
|
||||
};
|
||||
|
||||
const envContent = generateEnvFile(testConfig);
|
||||
|
||||
console.log(chalk.green('✅ 生成的 .env 内容:'));
|
||||
console.log(chalk.gray('─'.repeat(40)));
|
||||
console.log(envContent);
|
||||
console.log(chalk.gray('─'.repeat(40)));
|
||||
|
||||
// 测试解析生成的内容
|
||||
const lines = envContent.split('\n');
|
||||
const parsedEnv = {};
|
||||
|
||||
lines.forEach(line => {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed && !trimmed.startsWith('#')) {
|
||||
const [key, ...valueParts] = trimmed.split('=');
|
||||
if (key && valueParts.length > 0) {
|
||||
parsedEnv[key.trim()] = valueParts.join('=').trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const reparsedConfig = parseConfig(parsedEnv);
|
||||
const isConsistent = JSON.stringify(testConfig) === JSON.stringify(reparsedConfig);
|
||||
|
||||
if (isConsistent) {
|
||||
console.log(chalk.green('✅ 生成的内容解析正确'));
|
||||
} else {
|
||||
console.log(chalk.red('❌ 生成的内容解析不一致'));
|
||||
console.log('原始配置:', testConfig);
|
||||
console.log('重新解析:', reparsedConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试配置摘要生成
|
||||
*/
|
||||
function testConfigSummary() {
|
||||
console.log(chalk.blue('\n🧪 测试配置摘要生成...'));
|
||||
|
||||
const testConfig = {
|
||||
PORT: 5173,
|
||||
HOST: '0.0.0.0',
|
||||
PUBLIC_DIR: 'dist',
|
||||
API_TARGET: 'http://localhost:3000',
|
||||
API_PREFIX: '/api',
|
||||
DEV_MODE: true,
|
||||
CORS_ENABLED: true,
|
||||
CORS_ORIGIN: '*',
|
||||
SPA_FALLBACK_ENABLED: true,
|
||||
SPA_EXCLUDE_EXTENSIONS: '.js,.css,.png,.jpg'
|
||||
};
|
||||
|
||||
const summary = getConfigSummary(testConfig);
|
||||
|
||||
console.log(chalk.green('✅ 生成的摘要:'));
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
|
||||
// 验证摘要结构
|
||||
const requiredKeys = ['server', 'proxy', 'spa', 'cors'];
|
||||
const hasAllKeys = requiredKeys.every(key => key in summary);
|
||||
|
||||
if (hasAllKeys) {
|
||||
console.log(chalk.green('✅ 摘要包含所有必需的键'));
|
||||
} else {
|
||||
console.log(chalk.red('❌ 摘要缺少必需的键'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有测试
|
||||
*/
|
||||
function runAllTests() {
|
||||
console.log(chalk.cyan('🚀 SPA 预览服务器配置测试\n'));
|
||||
|
||||
try {
|
||||
testConfigParsing();
|
||||
testConfigValidation();
|
||||
testEnvFileGeneration();
|
||||
testConfigSummary();
|
||||
|
||||
console.log(chalk.green('\n🎉 所有测试完成!'));
|
||||
console.log(chalk.blue('\n📝 测试摘要:'));
|
||||
console.log(chalk.green('✅ 配置解析'));
|
||||
console.log(chalk.green('✅ 配置验证'));
|
||||
console.log(chalk.green('✅ .env 文件生成'));
|
||||
console.log(chalk.green('✅ 配置摘要'));
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red('\n❌ 测试失败:'), error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接调用则运行测试
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
runAllTests();
|
||||
}
|
||||
|
||||
export { runAllTests };
|
||||
Reference in New Issue
Block a user