import { existsSync, promises } from 'node:fs'; import { resolve, extname, dirname } from 'pathe'; import * as dotenv from 'dotenv'; import { rmdir } from 'node:fs/promises'; import { homedir } from 'node:os'; import createJiti from 'jiti'; import * as rc9 from 'rc9'; import { defu } from 'defu'; import { findWorkspaceDir } from 'pkg-types'; async function setupDotenv(options) { const targetEnvironment = options.env ?? process.env; const environment = await loadDotenv({ cwd: options.cwd, fileName: options.fileName ?? ".env", env: targetEnvironment, interpolate: options.interpolate ?? true }); for (const key in environment) { if (!key.startsWith("_") && targetEnvironment[key] === void 0) { targetEnvironment[key] = environment[key]; } } return environment; } async function loadDotenv(options) { const environment = /* @__PURE__ */ Object.create(null); const dotenvFile = resolve(options.cwd, options.fileName); if (existsSync(dotenvFile)) { const parsed = dotenv.parse(await promises.readFile(dotenvFile, "utf8")); Object.assign(environment, parsed); } if (!options.env._applied) { Object.assign(environment, options.env); environment._applied = true; } if (options.interpolate) { interpolate(environment); } return environment; } function interpolate(target, source = {}, parse = (v) => v) { function getValue(key) { return source[key] !== void 0 ? source[key] : target[key]; } function interpolate2(value, parents = []) { if (typeof value !== "string") { return value; } const matches = value.match(/(.?\${?(?:[\w:]+)?}?)/g) || []; return parse(matches.reduce((newValue, match) => { const parts = /(.?)\${?([\w:]+)?}?/g.exec(match); const prefix = parts[1]; let value2, replacePart; if (prefix === "\\") { replacePart = parts[0]; value2 = replacePart.replace("\\$", "$"); } else { const key = parts[2]; replacePart = parts[0].slice(prefix.length); if (parents.includes(key)) { console.warn(`Please avoid recursive environment variables ( loop: ${parents.join(" > ")} > ${key} )`); return ""; } value2 = getValue(key); value2 = interpolate2(value2, [...parents, key]); } return value2 !== void 0 ? newValue.replace(replacePart, value2) : newValue; }, value)); } for (const key in target) { target[key] = interpolate2(getValue(key)); } } async function loadConfig(options) { options.cwd = resolve(process.cwd(), options.cwd || "."); options.name = options.name || "config"; options.configFile = options.configFile ?? (options.name !== "config" ? `${options.name}.config` : "config"); options.rcFile = options.rcFile ?? `.${options.name}rc`; if (options.extend !== false) { options.extend = { extendKey: "extends", ...options.extend }; } options.jiti = options.jiti || createJiti(void 0, { interopDefault: true, requireCache: false, esmResolve: true, ...options.jitiOptions }); const r = { config: {}, cwd: options.cwd, configFile: resolve(options.cwd, options.configFile), layers: [] }; if (options.dotenv) { await setupDotenv({ cwd: options.cwd, ...options.dotenv === true ? {} : options.dotenv }); } const { config, configFile } = await resolveConfig(".", options); if (configFile) { r.configFile = configFile; } const configRC = {}; if (options.rcFile) { if (options.globalRc) { Object.assign(configRC, rc9.readUser({ name: options.rcFile, dir: options.cwd })); const workspaceDir = await findWorkspaceDir(options.cwd).catch(() => { }); if (workspaceDir) { Object.assign(configRC, rc9.read({ name: options.rcFile, dir: workspaceDir })); } } Object.assign(configRC, rc9.read({ name: options.rcFile, dir: options.cwd })); } r.config = defu( options.overrides, config, configRC, options.defaultConfig ); if (options.extend) { await extendConfig(r.config, options); r.layers = r.config._layers; delete r.config._layers; r.config = defu( r.config, ...r.layers.map((e) => e.config) ); } const baseLayers = [ options.overrides && { config: options.overrides, configFile: void 0, cwd: void 0 }, { config, configFile: options.configFile, cwd: options.cwd }, options.rcFile && { config: configRC, configFile: options.rcFile } ].filter((l) => l && l.config); r.layers = [ ...baseLayers, ...r.layers ]; if (options.defaults) { r.config = defu(r.config, options.defaults); } return r; } async function extendConfig(config, options) { config._layers = config._layers || []; if (!options.extend) { return; } let keys = options.extend.extendKey; if (typeof keys === "string") { keys = [keys]; } const extendSources = []; for (const key of keys) { extendSources.push(...(Array.isArray(config[key]) ? config[key] : [config[key]]).filter(Boolean)); delete config[key]; } for (const extendSource of extendSources) { if (typeof extendSource !== "string") { console.warn(`Cannot extend config from \`${JSON.stringify(extendSource)}\` (which should be a string) in ${options.cwd}`); continue; } const _config = await resolveConfig(extendSource, options); if (!_config.config) { console.warn(`Cannot extend config from \`${extendSource}\` in ${options.cwd}`); continue; } await extendConfig(_config.config, { ...options, cwd: _config.cwd }); config._layers.push(_config); if (_config.config._layers) { config._layers.push(..._config.config._layers); delete _config.config._layers; } } } const GIT_PREFIXES = ["github:", "gitlab:", "bitbucket:", "https://"]; const NPM_PACKAGE_RE = /^(@[\da-z~-][\d._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*$/; async function resolveConfig(source, options) { if (options.resolve) { const res2 = await options.resolve(source, options); if (res2) { return res2; } } if (GIT_PREFIXES.some((prefix) => source.startsWith(prefix))) { const { downloadTemplate } = await import('giget'); const url = new URL(source); const gitRepo = url.protocol + url.pathname.split("/").slice(0, 2).join("/"); const name = gitRepo.replace(/[#/:@\\]/g, "_"); const tmpDir = process.env.XDG_CACHE_HOME ? resolve(process.env.XDG_CACHE_HOME, "c12", name) : resolve(homedir(), ".cache/c12", name); if (existsSync(tmpDir)) { await rmdir(tmpDir, { recursive: true }); } const clonned = await downloadTemplate(source, { dir: tmpDir }); source = clonned.dir; } if (NPM_PACKAGE_RE.test(source)) { try { source = options.jiti.resolve(source, { paths: [options.cwd] }); } catch { } } const isDir = !extname(source); const cwd = resolve(options.cwd, isDir ? source : dirname(source)); if (isDir) { source = options.configFile; } const res = { config: void 0, cwd }; try { res.configFile = options.jiti.resolve(resolve(cwd, source), { paths: [cwd] }); } catch { } if (!existsSync(res.configFile)) { return res; } res.config = options.jiti(res.configFile); if (typeof res.config === "function") { res.config = await res.config(); } return res; } export { loadConfig, loadDotenv, setupDotenv };