|
- 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, readPackageJSON } 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(
- // eslint-disable-next-line unicorn/no-array-reduce
- 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.envName = options.envName ?? process.env.NODE_ENV;
- 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 })
- );
- }
- const pkgJson = {};
- if (options.packageJson) {
- const keys = (Array.isArray(options.packageJson) ? options.packageJson : [
- typeof options.packageJson === "string" ? options.packageJson : options.name
- ]).filter((t) => t && typeof t === "string");
- const pkgJsonFile = await readPackageJSON(options.cwd).catch(() => {
- });
- const values = keys.map((key) => pkgJsonFile?.[key]);
- Object.assign(pkgJson, defu({}, ...values));
- }
- r.config = defu(
- options.overrides,
- config,
- configRC,
- pkgJson,
- 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 },
- options.packageJson && { config: pkgJson, configFile: "package.json" }
- ].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 (let extendSource of extendSources) {
- const originalExtendSource = extendSource;
- let sourceOptions = {};
- if (extendSource.source) {
- sourceOptions = extendSource.options || {};
- extendSource = extendSource.source;
- }
- if (Array.isArray(extendSource)) {
- sourceOptions = extendSource[1] || {};
- extendSource = extendSource[0];
- }
- if (typeof extendSource !== "string") {
- console.warn(
- `Cannot extend config from \`${JSON.stringify(
- originalExtendSource
- )}\` in ${options.cwd}`
- );
- continue;
- }
- const _config = await resolveConfig(extendSource, options, sourceOptions);
- 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, sourceOptions = {}) {
- 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 cloned = await downloadTemplate(source, { dir: tmpDir });
- source = cloned.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, source, sourceOptions };
- 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 (res.config instanceof Function) {
- res.config = await res.config();
- }
- if (options.envName) {
- const envConfig = {
- ...res.config["$" + options.envName],
- ...res.config.$env?.[options.envName]
- };
- if (Object.keys(envConfig).length > 0) {
- res.config = defu(envConfig, res.config);
- }
- }
- res.meta = defu(res.sourceOptions.meta, res.config.$meta);
- delete res.config.$meta;
- if (res.sourceOptions.overrides) {
- res.config = defu(res.sourceOptions.overrides, res.config);
- }
- return res;
- }
-
- export { loadConfig, loadDotenv, setupDotenv };
|