【新增】私有证书

This commit is contained in:
cai
2025-09-03 15:15:59 +08:00
parent efd052a297
commit 954cd1638d
442 changed files with 76787 additions and 7483 deletions

View 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 };

View 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
}
};
}

View 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 };