everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
495 lines (493 loc) • 22 kB
JavaScript
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