UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

495 lines (493 loc) 22 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const require_runtime = require('./_virtual/_rolldown/runtime.cjs'); const require_fastkv = require('./fastkv.cjs'); const require_merge = require('./merge.cjs'); const require_network = require('./network.cjs'); const require_types = require('./types.cjs'); let node_fs = require("node:fs"); let node_path = require("node:path"); //#region src/config.ts const LOCAL_PREFIX = "local:"; const DEFAULT_HOST_PORT = 3e3; const RESOLVED_CONFIG_FILENAME = "bos.resolved-config.json"; let cachedConfig = null; let projectRoot = null; function clearConfigCache() { cachedConfig = null; projectRoot = null; } function findConfigPath(cwd) { let dir = cwd ?? process.cwd(); while (dir !== "/") { const configPath = (0, node_path.join)(dir, "bos.config.json"); if ((0, node_fs.existsSync)(configPath)) return configPath; dir = (0, node_path.dirname)(dir); } return null; } function getConfig() { return cachedConfig; } function getProjectRoot() { if (!projectRoot) throw new Error("Config not loaded. Call loadConfig() first."); return projectRoot; } async function loadConfig(options) { const configPath = options?.path ?? findConfigPath(options?.cwd); if (!configPath) { projectRoot = options?.cwd ?? process.cwd(); return null; } const baseDir = (0, node_path.dirname)(configPath); const env = options?.env ?? "development"; const runtimeEnv = env === "staging" ? "production" : env; try { const extendedChain = []; const parsed = await resolveConfigWithExtends(configPath, baseDir, /* @__PURE__ */ new Set(), extendedChain, env); const config = await resolveRootComposableEntries(require_types.BosConfigSchema.parse(parsed), baseDir, runtimeEnv); cachedConfig = config; projectRoot = baseDir; return { config, runtime: buildRuntimeConfig(config, baseDir, runtimeEnv, { plugins: await resolveRuntimePlugins(config.plugins ?? {}, baseDir, runtimeEnv) }), source: { path: configPath, extended: extendedChain.length > 0 ? extendedChain : void 0, remote: extendedChain.some((entry) => entry.startsWith("bos://")) } }; } catch (error) { throw new Error(`Failed to load config from ${configPath}: ${error}`); } } async function loadBosConfig(options) { const result = await loadConfig(options); if (!result) throw new Error("No bos.config.json found"); return result.runtime; } async function buildRuntimePluginsForConfig(config, baseDir, env) { const plugins = await resolveRuntimePlugins(config.plugins ?? {}, baseDir, env); return Object.keys(plugins).length > 0 ? plugins : void 0; } async function resolveRootComposableEntries(config, baseDir, env) { const resolvedApi = await resolveComposableReference(config.app.api, baseDir, env, "app.api"); const resolvedAuth = config.app.auth ? await resolveComposableReference(config.app.auth, baseDir, env, "app.auth") : void 0; return { ...config, app: { ...config.app, api: resolvedApi.entry, auth: resolvedAuth?.entry } }; } function getResolvedConfigPath(configDir) { return (0, node_path.join)(configDir, ".bos", RESOLVED_CONFIG_FILENAME); } function loadResolvedConfig(configDir) { const resolvedPath = getResolvedConfigPath(configDir); if (!(0, node_fs.existsSync)(resolvedPath)) return null; try { const raw = JSON.parse((0, node_fs.readFileSync)(resolvedPath, "utf-8")); if (!require_merge.isPlainObject(raw)) return null; const { _resolved, ...configData } = raw; return require_types.BosConfigSchema.parse(configData); } catch { return null; } } function writeResolvedConfig(configDir, config, env, extendsChain, source) { const resolvedPath = getResolvedConfigPath(configDir); const resolvedDir = (0, node_path.dirname)(resolvedPath); if (!(0, node_fs.existsSync)(resolvedDir)) (0, node_fs.mkdirSync)(resolvedDir, { recursive: true }); const ordered = require_merge.rebuildOrderedConfig(config); const output = { _resolved: { env, resolvedAt: (/* @__PURE__ */ new Date()).toISOString(), extendsChain: extendsChain ?? [], ...source ? { source } : {} }, ...ordered }; const content = `${JSON.stringify(output, null, 2)}\n`; try { if ((0, node_fs.readFileSync)(resolvedPath, "utf-8") === content) return; } catch {} (0, node_fs.writeFileSync)(resolvedPath, content); } function resolveBosConfigPath(configDir) { const resolvedPath = getResolvedConfigPath(configDir); if ((0, node_fs.existsSync)(resolvedPath)) return resolvedPath; return (0, node_path.join)(configDir, "bos.config.json"); } function readBosConfigForBuild(configDir) { const resolvedPath = getResolvedConfigPath(configDir); if ((0, node_fs.existsSync)(resolvedPath)) try { const raw = JSON.parse((0, node_fs.readFileSync)(resolvedPath, "utf-8")); if (require_merge.isPlainObject(raw)) { const { _resolved, ...configData } = raw; return configData; } } catch {} const bosConfigPath = (0, node_path.join)(configDir, "bos.config.json"); return JSON.parse((0, node_fs.readFileSync)(bosConfigPath, "utf-8")); } function parseExtendsTarget(ref) { const hashIndex = ref.indexOf("#"); if (hashIndex === -1) return { configPath: ref }; const configPath = ref.slice(0, hashIndex); const targetPath = ref.slice(hashIndex + 1); return { configPath, targetPath: targetPath.length > 0 ? targetPath : void 0 }; } function getConfigBaseDir(configPath, baseDir) { if (configPath.startsWith("bos://")) return baseDir; return (0, node_path.dirname)((0, node_path.isAbsolute)(configPath) ? configPath : (0, node_path.resolve)(baseDir, configPath)); } function asComposableEntry(value) { if (typeof value === "string") return { extends: value }; if (!require_merge.isPlainObject(value)) throw new Error(`Expected config entry object, received ${typeof value}`); return value; } function getTargetedEntry(config, targetPath) { if (targetPath === "app.api") return asComposableEntry(config.app?.api); if (targetPath === "app.auth") return asComposableEntry(config.app?.auth); if (targetPath.startsWith("plugins.")) { const pluginId = targetPath.slice(8); if (pluginId.length === 0) throw new Error(`Invalid plugin target path: ${targetPath}`); return asComposableEntry(config.plugins?.[pluginId]); } throw new Error(`Unsupported extends target path: ${targetPath}`); } function getAssociatedUi(config, _targetPath) { return require_merge.isPlainObject(config.app?.ui) ? config.app.ui : void 0; } function mergeComposableEntries(parent, child) { return require_merge.bosConfigMerger({ ...child }, parent); } function stripUnsafeLocalDevelopment(entry, allowLocalPaths) { if (!entry || allowLocalPaths) return entry; if (typeof entry.development === "string" && entry.development.startsWith(LOCAL_PREFIX)) { const { development: _ignored, ...rest } = entry; return rest; } return entry; } async function resolveComposableReference(source, baseDir, env, defaultTargetPath) { let resolvedEntry = {}; let providerBaseDir = baseDir; let targetPath = defaultTargetPath; let associatedUi; let allowLocalPaths = false; let extendsError; const extendsRef = source.extends ? require_merge.resolveExtendsRef(source.extends, env) : void 0; if (extendsRef) { const parsed = parseExtendsTarget(extendsRef); targetPath = parsed.targetPath ?? defaultTargetPath; const extendsBaseDir = getConfigBaseDir(parsed.configPath, baseDir); try { const extendedConfig = await resolveConfigWithExtends(parsed.configPath, extendsBaseDir, /* @__PURE__ */ new Set(), [], env); resolvedEntry = mergeComposableEntries(resolvedEntry, getTargetedEntry(extendedConfig, targetPath)); providerBaseDir = extendsBaseDir; associatedUi = getAssociatedUi(extendedConfig, targetPath); } catch (error) { extendsError = error; } } const localDevelopment = typeof source.development === "string" && source.development.startsWith(LOCAL_PREFIX) ? source.development : void 0; if (localDevelopment) { const localPath = (0, node_path.resolve)(baseDir, localDevelopment.slice(6).trim()); const localConfigPath = (0, node_path.join)(localPath, "bos.config.json"); if ((0, node_fs.existsSync)(localConfigPath)) { const localConfig = await resolveConfigWithExtends(localConfigPath, localPath, /* @__PURE__ */ new Set(), [], env); resolvedEntry = mergeComposableEntries(resolvedEntry, getTargetedEntry(localConfig, targetPath)); providerBaseDir = localPath; associatedUi = getAssociatedUi(localConfig, targetPath); allowLocalPaths = true; } } const sourceOverrides = { ...source }; if (allowLocalPaths && localDevelopment) delete sourceOverrides.development; resolvedEntry = mergeComposableEntries(resolvedEntry, sourceOverrides); if (extendsError && !allowLocalPaths && typeof resolvedEntry.development !== "string" && typeof resolvedEntry.production !== "string" && typeof resolvedEntry.name !== "string") throw extendsError; return { entry: stripUnsafeLocalDevelopment(resolvedEntry, allowLocalPaths || Boolean(localDevelopment)), providerBaseDir, targetPath, associatedUi: stripUnsafeLocalDevelopment(associatedUi, allowLocalPaths) }; } function resolveDevelopmentTarget(development, production, baseDir, forceSource) { if (forceSource === "remote") return resolveRuntimeTarget(production, baseDir, "remote"); if (!development) return resolveRuntimeTarget(production, baseDir, "remote"); const devTarget = resolveRuntimeTarget(development, baseDir); if (devTarget.source === "local" && (!devTarget.localPath || !(0, node_fs.existsSync)(devTarget.localPath))) return resolveRuntimeTarget(production, baseDir, "remote"); return devTarget; } function buildRuntimeConfig(config, baseDir, env, options) { const uiConfig = config.app.ui; const apiConfig = config.app.api; const authConfig = config.app.auth; const uiRuntime = env === "development" ? resolveDevelopmentTarget(uiConfig.development, uiConfig.production, baseDir, options?.uiSource) : resolveRuntimeTarget(uiConfig.production, baseDir, "remote"); const apiRuntime = env === "development" ? resolveDevelopmentTarget(apiConfig.development, apiConfig.production, baseDir, options?.apiSource) : resolveRuntimeTarget(apiConfig.production, baseDir, "remote"); const authRuntime = authConfig ? env === "development" ? resolveDevelopmentTarget(authConfig.development, authConfig.production, baseDir, options?.authSource) : resolveRuntimeTarget(authConfig.production, baseDir, "remote") : void 0; const hostConfig = config.app.host; const hostRuntime = env === "development" ? resolveDevelopmentTarget(hostConfig.development, hostConfig.production, baseDir, options?.hostSource) : resolveRuntimeTarget(hostConfig.production, baseDir, "remote"); const hostListeningUrl = env === "development" ? resolveDevelopmentHostUrl(hostConfig.development) : `http://localhost:${hostRuntime.port ?? DEFAULT_HOST_PORT}`; const hostIsRemote = hostRuntime.source === "remote"; const uiIsRemote = uiRuntime.source === "remote"; const apiIsRemote = apiRuntime.source === "remote"; const resolvedApiName = resolvePluginRuntimeName(apiConfig.name, apiRuntime.localPath, "api"); return { env, account: config.account, domain: config.domain, title: config.title, description: config.description, networkId: require_network.getNetworkIdForAccount(config.account), repository: config.repository, host: { name: "host", url: hostListeningUrl, entry: `${hostListeningUrl}/mf-manifest.json`, localPath: hostRuntime.localPath, port: hostRuntime.port ?? DEFAULT_HOST_PORT, secrets: hostConfig.secrets, integrity: hostIsRemote ? hostConfig.integrity : void 0, source: hostRuntime.source, remoteUrl: hostIsRemote ? hostRuntime.url : void 0 }, shared: config.shared, ui: { name: uiConfig.name, url: uiRuntime.url, entry: uiRuntime.url ? `${uiRuntime.url}/mf-manifest.json` : "/mf-manifest.json", localPath: uiRuntime.localPath, port: uiRuntime.port, ssrUrl: uiIsRemote ? uiConfig.ssr : void 0, ssrIntegrity: uiIsRemote ? uiConfig.ssrIntegrity : void 0, integrity: uiIsRemote ? uiConfig.integrity : void 0, source: uiRuntime.source }, api: { name: resolvedApiName, url: apiRuntime.url, entry: apiRuntime.url ? `${apiRuntime.url}/mf-manifest.json` : "/mf-manifest.json", localPath: apiRuntime.localPath, port: apiRuntime.port, source: apiRuntime.source, proxy: options?.proxy ?? apiConfig.proxy, variables: apiConfig.variables, secrets: apiConfig.secrets, integrity: apiIsRemote ? apiConfig.integrity : void 0 }, auth: (() => { if (!authConfig || !authRuntime) return void 0; return { name: resolvePluginRuntimeName(authConfig.name, authRuntime.localPath, "auth"), url: authRuntime.url, entry: authRuntime.url ? `${authRuntime.url}/mf-manifest.json` : "/mf-manifest.json", localPath: authRuntime.localPath, port: authRuntime.port, source: authRuntime.source, proxy: authConfig.proxy, variables: authConfig.variables, secrets: authConfig.secrets, integrity: authRuntime.source === "remote" ? authConfig.integrity : void 0, sidebar: authConfig.sidebar?.map((item) => ({ ...item, to: item.to ?? "/auth", roleRequired: item.roleRequired ?? "member" })) }; })(), plugins: options?.plugins && Object.keys(options.plugins).length > 0 ? options.plugins : void 0 }; } async function loadConfigFile(configPath, baseDir) { if (configPath.startsWith("bos://")) return require_fastkv.fetchBosConfigFromFastKv(configPath); const resolvedPath = (0, node_path.isAbsolute)(configPath) ? configPath : (0, node_path.resolve)(baseDir, configPath); return JSON.parse((0, node_fs.readFileSync)(resolvedPath, "utf-8")); } async function resolveConfigWithExtends(configPath, baseDir, visited, chain, env = "development") { if (visited.has(configPath)) throw new Error(`Circular extends detected: ${[...visited, configPath].join(" -> ")}`); const config = await loadConfigFile(configPath, baseDir); chain.push(configPath); if (!config.extends) return config; const extendsRef = require_merge.resolveExtendsRef(config.extends, env); if (!extendsRef) return config; const parsedParentRef = parseExtendsTarget(extendsRef); const nextVisited = new Set(visited); nextVisited.add(configPath); const parentBaseDir = getConfigBaseDir(parsedParentRef.configPath, baseDir); return require_merge.mergeBosConfigWithExtends(await resolveConfigWithExtends(parsedParentRef.configPath, parentBaseDir, nextVisited, chain, env), config); } function normalizePluginEntry(raw) { if (raw === null || raw === false) return raw; if (typeof raw === "string") return { extends: raw }; return raw; } async function resolveRuntimePlugins(plugins, baseDir, env) { const out = {}; for (const [pluginId, rawInput] of Object.entries(plugins)) { const normalized = normalizePluginEntry(rawInput); if (normalized === null || normalized === false) continue; const resolvedReference = await resolveComposableReference(normalized, baseDir, env, `plugins.${pluginId}`); const pluginRuntime = buildRuntimePluginConfig(pluginId, env, resolvedReference); if (!pluginRuntime.localPath && !pluginRuntime.url) continue; if (pluginRuntime.source === "remote" && pluginRuntime.url && !pluginRuntime.localPath && typeof resolvedReference.entry.name !== "string") pluginRuntime.name = await resolveRemotePluginRuntimeName(pluginRuntime.url, pluginRuntime.name); out[pluginId] = pluginRuntime; } return out; } async function resolveRemotePluginRuntimeName(baseUrl, fallback) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5e3); const response = await fetch(`${baseUrl.replace(/\/$/, "")}/plugin.manifest.json`, { signal: controller.signal }); clearTimeout(timeout); if (!response.ok) return fallback; const manifest = await response.json(); return typeof manifest.plugin?.name === "string" && manifest.plugin.name.length > 0 ? manifest.plugin.name : fallback; } catch { return fallback; } } function buildRuntimePluginConfig(pluginId, env, resolved) { const source = resolved.entry; const development = typeof source.development === "string" ? source.development : void 0; const production = typeof source.production === "string" ? source.production : void 0; if (production?.startsWith("bos://")) throw new Error(`Plugin "${pluginId}" has unsupported production target "${production}". Use extends: "bos://account/domain" for plugin configs or a CDN URL for production.`); const runtimeTarget = env === "development" ? resolveDevelopmentTarget(development, production, resolved.providerBaseDir) : resolveRuntimeTarget(production, resolved.providerBaseDir, "remote"); const apiName = resolvePluginRuntimeName(source.name, runtimeTarget.localPath, pluginId); const uiConfig = resolved.associatedUi; const uiDevelopment = typeof uiConfig?.development === "string" ? uiConfig.development : void 0; const uiProduction = typeof uiConfig?.production === "string" ? uiConfig.production : void 0; const uiRuntime = uiConfig && (uiDevelopment || uiProduction) ? env === "development" ? resolveDevelopmentTarget(uiDevelopment, uiProduction, resolved.providerBaseDir) : resolveRuntimeTarget(uiProduction, resolved.providerBaseDir, "remote") : void 0; const sidebar = source.sidebar?.map((item) => ({ ...item, to: item.to ?? `/${pluginId}`, roleRequired: item.roleRequired ?? "member" })); const routes = source.routes; return { name: apiName, url: runtimeTarget.url, entry: runtimeTarget.url ? `${runtimeTarget.url.replace(/\/$/, "")}/mf-manifest.json` : "/mf-manifest.json", source: runtimeTarget.source, localPath: runtimeTarget.localPath, port: runtimeTarget.port, proxy: typeof source.proxy === "string" ? source.proxy : void 0, variables: normalizeStringRecord(source.variables), secrets: normalizeStringArray(source.secrets), integrity: runtimeTarget.source === "remote" ? source.integrity : void 0, ui: uiRuntime ? { name: typeof uiConfig?.name === "string" ? uiConfig.name : `${apiName}-ui`, url: uiRuntime.url, entry: uiRuntime.url ? `${uiRuntime.url.replace(/\/$/, "")}/mf-manifest.json` : "/mf-manifest.json", source: uiRuntime.source, localPath: uiRuntime.localPath, port: uiRuntime.port, integrity: uiRuntime.source === "remote" && typeof uiConfig?.integrity === "string" ? uiConfig.integrity : void 0 } : void 0, sidebar, routes }; } function resolvePluginRuntimeName(explicitName, localPath, fallback) { if (explicitName) return explicitName; if (!localPath) return fallback; try { const packageJsonPath = (0, node_path.join)(localPath, "package.json"); const packageJson = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8")); if (typeof packageJson.name === "string" && packageJson.name.length > 0) return packageJson.name; } catch {} return fallback; } function normalizeStringRecord(value) { if (!require_merge.isPlainObject(value)) return void 0; const out = {}; for (const [key, raw] of Object.entries(value)) if (typeof raw === "string") out[key] = raw; return Object.keys(out).length > 0 ? out : void 0; } function normalizeStringArray(value) { if (!Array.isArray(value)) return void 0; const out = value.filter((item) => typeof item === "string" && item.length > 0); return out.length > 0 ? out : void 0; } function resolveRuntimeTarget(value, baseDir, defaultSource = "remote") { if (!value) return { source: defaultSource, url: "" }; if (value.startsWith(LOCAL_PREFIX)) { const localTarget = value?.slice(6).trim(); if (!localTarget) throw new Error(`Invalid local development target: ${value}`); const localPath = (0, node_path.resolve)(baseDir, localTarget); if (!(0, node_fs.existsSync)(localPath)) return { source: "local", url: "" }; return { source: "local", url: "", localPath }; } return { source: defaultSource, url: value.replace(/\/$/, ""), port: parsePort(value) }; } function isLocalDevelopmentTarget(value) { return typeof value === "string" && value.startsWith(LOCAL_PREFIX); } function resolveLocalDevelopmentPath(value, baseDir) { if (!isLocalDevelopmentTarget(value)) return null; const localTarget = value.slice(6).trim(); return localTarget ? (0, node_path.resolve)(baseDir, localTarget) : null; } function resolveDevelopmentHostUrl(value) { if (!value || isLocalDevelopmentTarget(value)) return `http://localhost:${DEFAULT_HOST_PORT}`; return value.replace(/\/$/, ""); } function getHostDevelopmentPort(value) { return parsePort(resolveDevelopmentHostUrl(value)); } function parsePort(url) { try { const parsed = new URL(url); return parsed.port ? parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80; } catch { return 3e3; } } //#endregion exports.BOS_CONFIG_ORDER = require_merge.BOS_CONFIG_ORDER; exports.BosConfigSchema = require_types.BosConfigSchema; exports.buildRuntimeConfig = buildRuntimeConfig; exports.buildRuntimePluginsForConfig = buildRuntimePluginsForConfig; exports.clearConfigCache = clearConfigCache; exports.findConfigPath = findConfigPath; exports.getConfig = getConfig; exports.getHostDevelopmentPort = getHostDevelopmentPort; exports.getProjectRoot = getProjectRoot; exports.getResolvedConfigPath = getResolvedConfigPath; exports.isLocalDevelopmentTarget = isLocalDevelopmentTarget; exports.loadBosConfig = loadBosConfig; exports.loadConfig = loadConfig; exports.loadResolvedConfig = loadResolvedConfig; exports.parsePort = parsePort; exports.readBosConfigForBuild = readBosConfigForBuild; exports.rebuildOrderedConfig = require_merge.rebuildOrderedConfig; exports.resolveBosConfigPath = resolveBosConfigPath; exports.resolveComposableReference = resolveComposableReference; exports.resolveDevelopmentHostUrl = resolveDevelopmentHostUrl; exports.resolveLocalDevelopmentPath = resolveLocalDevelopmentPath; exports.resolvePluginRuntimeName = resolvePluginRuntimeName; exports.writeResolvedConfig = writeResolvedConfig; //# sourceMappingURL=config.cjs.map