everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
571 lines (569 loc) • 25.1 kB
JavaScript
import { fetchBosConfigFromFastKv } from "./fastkv.mjs";
import { BOS_CONFIG_ORDER, bosConfigMerger, isPlainObject, mergeBosConfigWithExtends, rebuildOrderedConfig, resolveExtendsRef } from "./merge.mjs";
import { getNetworkIdForAccount } from "./network.mjs";
import { BosConfigSchema } from "./types.mjs";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, isAbsolute, join, resolve } from "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;
let configWarnings = [];
let suppressConfigWarnings = false;
function clearConfigCache() {
cachedConfig = null;
projectRoot = null;
configWarnings = [];
}
function suppressWarnings() {
suppressConfigWarnings = true;
}
function resumeWarnings() {
suppressConfigWarnings = false;
}
function drainConfigWarnings() {
const warnings = [...configWarnings];
configWarnings = [];
return warnings;
}
function emitConfigWarning(message) {
if (suppressConfigWarnings) configWarnings.push(message);
else console.warn(message);
}
function findConfigPath(cwd) {
let dir = cwd ?? process.cwd();
while (dir !== "/") {
const configPath = join(dir, "bos.config.json");
if (existsSync(configPath)) return configPath;
dir = dirname(dir);
}
return null;
}
function getConfig() {
return cachedConfig;
}
function getProjectRoot() {
if (!projectRoot) throw new Error("Config not loaded. Call loadResolvedConfig() first.");
return projectRoot;
}
async function loadLocalConfig(options) {
const configPath = options?.path ?? findConfigPath(options?.cwd);
if (!configPath) {
projectRoot = options?.cwd ?? process.cwd();
return null;
}
const baseDir = dirname(configPath);
const config = await loadConfigFile(configPath, baseDir);
projectRoot = baseDir;
return {
config,
source: { path: configPath }
};
}
async function loadResolvedConfig(options) {
const configPath = options?.path ?? findConfigPath(options?.cwd);
if (!configPath) {
projectRoot = options?.cwd ?? process.cwd();
return null;
}
const baseDir = dirname(configPath);
const env = options?.env ?? "development";
const runtimeEnv = env === "staging" ? "production" : env;
try {
suppressWarnings();
const extendedChain = [];
const parsed = await resolveConfigWithExtends(configPath, baseDir, /* @__PURE__ */ new Set(), extendedChain, env);
const config = await resolveConfigComposableEntries(BosConfigSchema.parse(parsed), baseDir, runtimeEnv);
cachedConfig = config;
projectRoot = baseDir;
const runtime = buildRuntimeConfig(config, baseDir, runtimeEnv, { plugins: await resolveRuntimePlugins(config.plugins ?? {}, baseDir, runtimeEnv) });
const warnings = drainConfigWarnings();
resumeWarnings();
return {
config,
runtime,
source: {
path: configPath,
extended: extendedChain.length > 0 ? extendedChain : void 0,
remote: extendedChain.some((entry) => entry.startsWith("bos://"))
},
warnings: warnings.length > 0 ? warnings : void 0
};
} catch (error) {
resumeWarnings();
throw new Error(`Failed to load config from ${configPath}: ${error}`);
}
}
async function loadBosConfig(options) {
const result = await loadResolvedConfig(options);
if (!result) throw new Error("No bos.config.json found");
return result.runtime;
}
async function loadRemoteConfig(bosUrl, env = "production") {
const runtimeEnv = env === "staging" ? "production" : env;
const extendedChain = [];
const parsed = await resolveConfigWithExtends(bosUrl, process.cwd(), /* @__PURE__ */ new Set(), extendedChain, env);
const config = await resolveConfigComposableEntries(BosConfigSchema.parse(parsed), process.cwd(), runtimeEnv);
return {
rawConfig: await loadConfigFile(bosUrl, process.cwd()),
config,
source: bosUrl,
extendsChain: extendedChain
};
}
function parseRuntimeOverrideTargets(value) {
if (!value) return [];
return [...new Set(value.split(",").map((entry) => entry.trim()).filter(Boolean))].map((entry) => {
if (entry === "ui" || entry === "api" || entry === "plugins") return entry;
if (entry.startsWith("plugins.") && entry.length > 8) return entry;
throw new Error(`Invalid runtime override target: ${entry}`);
});
}
function isRuntimeOverrideAllowed(allowedTargets, target) {
if (allowedTargets.includes(target)) return true;
if (target.startsWith("plugins.")) return allowedTargets.includes("plugins.*");
return false;
}
async function buildRuntimePluginsForConfig(config, baseDir, env) {
const plugins = await resolveRuntimePlugins(config.plugins ?? {}, baseDir, env);
return Object.keys(plugins).length > 0 ? plugins : void 0;
}
function getEntryAssociatedUi(entry) {
if (!isPlainObject(entry.app)) return;
const app = entry.app;
return isPlainObject(app.ui) ? app.ui : void 0;
}
function mergeAssociatedUi(parentUi, childUi) {
if (!parentUi) return childUi ? { ...childUi } : void 0;
if (!childUi) return { ...parentUi };
return bosConfigMerger({ ...childUi }, parentUi);
}
function withAssociatedUi(entry, associatedUi) {
if (!associatedUi) return entry;
const app = isPlainObject(entry.app) ? { ...entry.app } : {};
app.ui = mergeAssociatedUi(getEntryAssociatedUi(entry), associatedUi);
return {
...entry,
app
};
}
async function resolveConfigComposableEntries(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;
const resolvedPlugins = config.plugins ? Object.fromEntries(await Promise.all(Object.entries(config.plugins).map(async ([pluginId, pluginValue]) => {
const resolvedPlugin = await resolveComposableReference(asComposableEntry(pluginValue), baseDir, env, `plugins.${pluginId}`);
return [pluginId, withAssociatedUi(resolvedPlugin.entry, resolvedPlugin.associatedUi)];
}))) : void 0;
return {
...config,
app: {
...config.app,
api: resolvedApi.entry,
auth: resolvedAuth?.entry
},
plugins: resolvedPlugins
};
}
function getResolvedConfigPath(configDir) {
return join(configDir, ".bos", RESOLVED_CONFIG_FILENAME);
}
function loadGeneratedResolvedConfig(configDir) {
const resolvedPath = getResolvedConfigPath(configDir);
if (!existsSync(resolvedPath)) return null;
try {
const raw = JSON.parse(readFileSync(resolvedPath, "utf-8"));
if (!isPlainObject(raw)) return null;
const { _resolved, ...configData } = raw;
return BosConfigSchema.parse(configData);
} catch {
return null;
}
}
function writeResolvedConfig(configDir, config, env, extendsChain, source) {
const resolvedPath = getResolvedConfigPath(configDir);
const resolvedDir = dirname(resolvedPath);
if (!existsSync(resolvedDir)) mkdirSync(resolvedDir, { recursive: true });
const ordered = 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 (readFileSync(resolvedPath, "utf-8") === content) return;
} catch {}
writeFileSync(resolvedPath, content);
}
function resolveBosConfigPath(configDir) {
const resolvedPath = getResolvedConfigPath(configDir);
if (existsSync(resolvedPath)) return resolvedPath;
return join(configDir, "bos.config.json");
}
function readBosConfigForBuild(configDir) {
const resolvedPath = getResolvedConfigPath(configDir);
if (existsSync(resolvedPath)) try {
const raw = JSON.parse(readFileSync(resolvedPath, "utf-8"));
if (isPlainObject(raw)) {
const { _resolved, ...configData } = raw;
return configData;
}
} catch {}
const bosConfigPath = join(configDir, "bos.config.json");
return JSON.parse(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 dirname(isAbsolute(configPath) ? configPath : resolve(baseDir, configPath));
}
function asComposableEntry(value) {
if (value === void 0) return {};
if (typeof value === "string") return { extends: value };
if (!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 isPlainObject(config.app?.ui) ? config.app.ui : void 0;
}
function mergeComposableEntries(parent, child) {
return 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 = getEntryAssociatedUi(source);
let allowLocalPaths = false;
let extendsError;
const extendsRef = source.extends ? 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 = mergeAssociatedUi(associatedUi, getAssociatedUi(extendedConfig, targetPath));
} catch (error) {
extendsError = error;
}
}
const localDevelopment = typeof source.development === "string" && source.development.startsWith(LOCAL_PREFIX) ? source.development : void 0;
const localDevelopmentPath = localDevelopment ? resolve(baseDir, localDevelopment.slice(6).trim()) : void 0;
const hasUsableLocalDevelopment = Boolean(localDevelopmentPath && existsSync(localDevelopmentPath));
if (localDevelopmentPath) {
const localPath = localDevelopmentPath;
const localConfigPath = join(localPath, "bos.config.json");
if (existsSync(localConfigPath)) {
const localConfig = await resolveConfigWithExtends(localConfigPath, localPath, /* @__PURE__ */ new Set(), [], env);
resolvedEntry = mergeComposableEntries(resolvedEntry, getTargetedEntry(localConfig, targetPath));
providerBaseDir = localPath;
associatedUi = mergeAssociatedUi(associatedUi, getAssociatedUi(localConfig, targetPath));
allowLocalPaths = true;
}
}
const sourceOverrides = { ...source };
if (allowLocalPaths && localDevelopment) delete sourceOverrides.development;
resolvedEntry = mergeComposableEntries(resolvedEntry, sourceOverrides);
associatedUi = mergeAssociatedUi(associatedUi, getEntryAssociatedUi(sourceOverrides));
if (extendsError && !allowLocalPaths && !hasUsableLocalDevelopment) throw extendsError;
return {
entry: stripUnsafeLocalDevelopment(resolvedEntry, allowLocalPaths || Boolean(localDevelopment)),
providerBaseDir,
targetPath,
associatedUi: stripUnsafeLocalDevelopment(associatedUi, allowLocalPaths)
};
}
function resolveDevelopmentTarget(development, production, baseDir, forceSource, target, extendsRef) {
if (forceSource === "remote") return resolveRuntimeTarget(production, baseDir, "remote");
if (!development) {
if (production && target) if (extendsRef) emitConfigWarning(`[Config] Resolving "${target}" from ${extendsRef}`);
else emitConfigWarning(`[Config] No development target for "${target}", using production`);
return resolveRuntimeTarget(production, baseDir, "remote");
}
const devTarget = resolveRuntimeTarget(development, baseDir);
if (devTarget.source === "local" && (!devTarget.localPath || !existsSync(devTarget.localPath))) {
if (production && target) emitConfigWarning(`[Config] Could not load local target for "${target}", using production`);
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, "app.ui") : resolveRuntimeTarget(uiConfig.production, baseDir, "remote");
const apiRuntime = env === "development" ? resolveDevelopmentTarget(apiConfig.development, apiConfig.production, baseDir, options?.apiSource, "app.api") : resolveRuntimeTarget(apiConfig.production, baseDir, "remote");
const authExtendsRef = authConfig?.extends ? resolveExtendsRef(authConfig.extends, env) : void 0;
const authRuntime = authConfig ? env === "development" ? resolveDevelopmentTarget(authConfig.development, authConfig.production, baseDir, options?.authSource, "app.auth", authExtendsRef) : resolveRuntimeTarget(authConfig.production, baseDir, "remote") : void 0;
const hostConfig = config.app.host;
const hostRuntime = env === "development" ? resolveDevelopmentTarget(hostConfig.development, hostConfig.production, baseDir, options?.hostSource, "app.host") : 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: 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: resolvePluginRuntimeName(uiConfig.name, uiRuntime.localPath, "ui"),
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 fetchBosConfigFromFastKv(configPath);
const resolvedPath = isAbsolute(configPath) ? configPath : resolve(baseDir, configPath);
return JSON.parse(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 = 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 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 pluginExtendsRef = source.extends ? resolveExtendsRef(source.extends, env) : void 0;
const runtimeTarget = env === "development" ? resolveDevelopmentTarget(development, production, resolved.providerBaseDir, void 0, `plugins.${pluginId}`, pluginExtendsRef) : 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, void 0, `plugins.${pluginId}.ui`) : 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 = join(localPath, "package.json");
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
if (typeof packageJson.name === "string" && packageJson.name.length > 0) return packageJson.name;
} catch {}
return fallback;
}
function normalizeStringRecord(value) {
if (!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 = resolve(baseDir, localTarget);
if (!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 ? 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
export { BOS_CONFIG_ORDER, BosConfigSchema, buildRuntimeConfig, buildRuntimePluginsForConfig, clearConfigCache, drainConfigWarnings, findConfigPath, getConfig, getHostDevelopmentPort, getProjectRoot, getResolvedConfigPath, isLocalDevelopmentTarget, isRuntimeOverrideAllowed, loadBosConfig, loadGeneratedResolvedConfig, loadLocalConfig, loadRemoteConfig, loadResolvedConfig, mergeBosConfigWithExtends, parsePort, parseRuntimeOverrideTargets, readBosConfigForBuild, rebuildOrderedConfig, resolveBosConfigPath, resolveComposableReference, resolveDevelopmentHostUrl, resolveExtendsRef, resolveLocalDevelopmentPath, resolvePluginRuntimeName, resumeWarnings, suppressWarnings, writeResolvedConfig };
//# sourceMappingURL=config.mjs.map