config-loader.mjs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import { createRequire } from 'node:module';
  4. import { fileURLToPath, pathToFileURL } from 'node:url';
  5. const require = createRequire(import.meta.url);
  6. const SOURCE_DIR = path.dirname(fileURLToPath(import.meta.url));
  7. export const PACKAGE_ROOT = path.resolve(SOURCE_DIR, '..', '..', '..');
  8. const { resolveWorkspaceRoot } = require(path.join(PACKAGE_ROOT, 'mcp', 'src', 'core', 'runtime-context.js'));
  9. const { getProductMode, normalizeProductMode } = require(path.join(PACKAGE_ROOT, 'mcp', 'src', 'core', 'product-mode.js'));
  10. const DEFAULT_CONFIG = Object.freeze({
  11. edition: 'auto',
  12. dashboard: Object.freeze({
  13. enabled: true,
  14. port: 4320,
  15. }),
  16. personal: Object.freeze({
  17. polling: Object.freeze({
  18. enabled: true,
  19. retryMs: 10000,
  20. friendPollingEnabled: true,
  21. }),
  22. }),
  23. enterprise: Object.freeze({
  24. relay: Object.freeze({
  25. enabled: true,
  26. batchSize: 100,
  27. waitMs: 30000,
  28. retryMinMs: 1000,
  29. retryMaxMs: 60000,
  30. upstreamReconnectMs: 300000,
  31. upstreamRetryMs: 60000,
  32. deviceRefreshMs: 60000,
  33. }),
  34. }),
  35. });
  36. function mergeConfig(base, override) {
  37. if (!override || typeof override !== 'object' || Array.isArray(override)) return base;
  38. const output = { ...base };
  39. for (const [key, value] of Object.entries(override)) {
  40. output[key] = value && typeof value === 'object' && !Array.isArray(value)
  41. ? mergeConfig(base?.[key] || {}, value)
  42. : value;
  43. }
  44. return output;
  45. }
  46. function normalizeConfig(config = {}) {
  47. const merged = mergeConfig(structuredClone(DEFAULT_CONFIG), config);
  48. merged.dashboard.port = Math.max(1, Math.min(65535, Number(merged.dashboard.port) || 4320));
  49. merged.personal.polling.retryMs = Math.max(3000, Number(merged.personal.polling.retryMs) || 10000);
  50. merged.enterprise.relay.batchSize = Math.max(1, Math.min(500, Number(merged.enterprise.relay.batchSize) || 100));
  51. merged.enterprise.relay.waitMs = Math.max(1000, Math.min(60000, Number(merged.enterprise.relay.waitMs) || 30000));
  52. merged.enterprise.relay.retryMinMs = Math.max(250, Number(merged.enterprise.relay.retryMinMs) || 1000);
  53. merged.enterprise.relay.retryMaxMs = Math.max(merged.enterprise.relay.retryMinMs, Number(merged.enterprise.relay.retryMaxMs) || 60000);
  54. merged.enterprise.relay.upstreamReconnectMs = Math.max(30000, Number(merged.enterprise.relay.upstreamReconnectMs) || 300000);
  55. merged.enterprise.relay.upstreamRetryMs = Math.max(5000, Number(merged.enterprise.relay.upstreamRetryMs) || 60000);
  56. merged.enterprise.relay.deviceRefreshMs = Math.max(10000, Number(merged.enterprise.relay.deviceRefreshMs) || 60000);
  57. return merged;
  58. }
  59. export function resolveRuntimeMode(config = {}, forceMode = '') {
  60. const forced = normalizeProductMode(forceMode);
  61. if (forced) return forced;
  62. const configured = normalizeProductMode(config.edition);
  63. if (configured) return configured;
  64. return getProductMode().mode;
  65. }
  66. export async function loadRuntimeConfig(options = {}) {
  67. const workspaceRoot = resolveWorkspaceRoot({
  68. packageRoot: PACKAGE_ROOT,
  69. workspaceRoot: options.workspaceRoot,
  70. });
  71. const explicitPath = String(options.configPath || process.env.QIWEI_RUNTIME_CONFIG || '').trim();
  72. const projectPath = path.join(workspaceRoot, 'qiwei.runtime.config.mjs');
  73. const examplePath = path.join(PACKAGE_ROOT, 'qiwei.runtime.config.example.mjs');
  74. const configPath = explicitPath
  75. ? path.resolve(workspaceRoot, explicitPath)
  76. : fs.existsSync(projectPath)
  77. ? projectPath
  78. : examplePath;
  79. let loaded = {};
  80. if (fs.existsSync(configPath)) {
  81. const version = fs.statSync(configPath).mtimeMs;
  82. const module = await import(`${pathToFileURL(configPath).href}?v=${version}`);
  83. loaded = module.default || {};
  84. }
  85. const config = normalizeConfig(loaded);
  86. return {
  87. config,
  88. configPath,
  89. workspaceRoot,
  90. packageRoot: PACKAGE_ROOT,
  91. mode: resolveRuntimeMode(config, options.forceMode),
  92. };
  93. }
  94. export { DEFAULT_CONFIG, mergeConfig, normalizeConfig };