Skip to content
This repository was archived by the owner on Mar 29, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions src/dotenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,7 @@ export async function loadDotenv(options: DotenvOptions): Promise<Env> {
}

// Based on https://github.com/motdotla/dotenv-expand
function interpolate(
target: Record<string, any>,
source: Record<string, any> = {},
parse = (v: any) => v,
) {
function interpolate(target: Record<string, any>, source: Record<string, any> = {}, parse = (v: any) => v) {
function getValue(key: string) {
// Source value 'wins' over target value
return source[key] === undefined ? target[key] : source[key];
Expand Down Expand Up @@ -137,11 +133,7 @@ function interpolate(

// Avoid recursion
if (parents.includes(key)) {
console.warn(
`Please avoid recursive environment variables ( loop: ${parents.join(
" > ",
)} > ${key} )`,
);
console.warn(`Please avoid recursive environment variables ( loop: ${parents.join(" > ")} > ${key} )`);
return "";
}

Expand All @@ -151,9 +143,7 @@ function interpolate(
value = interpolate(value, [...parents, key]);
}

return value === undefined
? newValue
: newValue.replace(replacePart, value);
return value === undefined ? newValue : newValue.replace(replacePart, value);
}, value),
);
}
Expand Down
94 changes: 22 additions & 72 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ export async function loadConfig<
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" ? "config" : `${options.name}.config`);
options.configFile = options.configFile ?? (options.name === "config" ? "config" : `${options.name}.config`);
options.rcFile = options.rcFile ?? `.${options.name}rc`;
if (options.extend !== false) {
options.extend = {
Expand Down Expand Up @@ -173,11 +171,7 @@ export async function loadConfig<
const keys = (
Array.isArray(options.packageJson)
? options.packageJson
: [
typeof options.packageJson === "string"
? options.packageJson
: options.name,
]
: [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]);
Expand All @@ -189,19 +183,11 @@ export async function loadConfig<
// TODO: #253 change order from defaults to overrides in next major version
for (const key in rawConfigs) {
const value = rawConfigs[key as ConfigSource];
configs[key as ConfigSource] = await (typeof value === "function"
? value({ configs, rawConfigs })
: value);
configs[key as ConfigSource] = await (typeof value === "function" ? value({ configs, rawConfigs }) : value);
}

// Combine sources
r.config = _merger(
configs.overrides,
configs.main,
configs.rc,
configs.packageJson,
configs.defaultConfig,
) as T;
r.config = _merger(configs.overrides, configs.main, configs.rc, configs.packageJson, configs.defaultConfig) as T;

// Allow extending
if (options.extend) {
Expand Down Expand Up @@ -252,10 +238,10 @@ export async function loadConfig<
return r;
}

async function extendConfig<
T extends UserInputConfig = UserInputConfig,
MT extends ConfigLayerMeta = ConfigLayerMeta,
>(config: InputConfig<T, MT>, options: LoadConfigOptions<T, MT>) {
async function extendConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(
config: InputConfig<T, MT>,
options: LoadConfigOptions<T, MT>,
) {
(config as any)._layers = config._layers || [];
if (!options.extend) {
return;
Expand All @@ -265,9 +251,7 @@ async function extendConfig<
keys = [keys];
}
const extendSources: Array<
| string
| [string, SourceOptions<T, MT>?]
| { source: string; options?: SourceOptions<T, MT> }
string | [string, SourceOptions<T, MT>?] | { source: string; options?: SourceOptions<T, MT> }
> = [];
for (const key of keys) {
const value = config[key];
Expand All @@ -278,11 +262,7 @@ async function extendConfig<
for (let extendSource of extendSources) {
const originalExtendSource = extendSource;
let sourceOptions: SourceOptions<T, MT> = {};
if (
typeof extendSource === "object" &&
extendSource !== null &&
"source" in extendSource
) {
if (typeof extendSource === "object" && extendSource !== null && "source" in extendSource) {
sourceOptions = extendSource.options || {};
extendSource = extendSource.source;
}
Expand All @@ -293,20 +273,14 @@ async function extendConfig<
if (typeof extendSource !== "string") {
// TODO: Use error in next major versions

console.warn(
`Cannot extend config from \`${JSON.stringify(
originalExtendSource,
)}\` in ${options.cwd}`,
);
console.warn(`Cannot extend config from \`${JSON.stringify(originalExtendSource)}\` in ${options.cwd}`);
continue;
}
const _config = await resolveConfig(extendSource, options, sourceOptions);
if (!_config.config) {
// TODO: Use error in next major versions

console.warn(
`Cannot extend config from \`${extendSource}\` in ${options.cwd}`,
);
console.warn(`Cannot extend config from \`${extendSource}\` in ${options.cwd}`);
continue;
}
await extendConfig(_config.config, {
Expand All @@ -323,23 +297,12 @@ async function extendConfig<
}

// TODO: Either expose from giget directly or redirect all non file:// protocols to giget
const GIGET_PREFIXES = [
"gh:",
"github:",
"gitlab:",
"bitbucket:",
"https://",
"http://",
];
const GIGET_PREFIXES = ["gh:", "github:", "gitlab:", "bitbucket:", "https://", "http://"];

// https://github.com/dword-design/package-name-regex
const NPM_PACKAGE_RE =
/^(@[\da-z~-][\d._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*($|\/.*)/;
const NPM_PACKAGE_RE = /^(@[\da-z~-][\d._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*($|\/.*)/;

async function resolveConfig<
T extends UserInputConfig = UserInputConfig,
MT extends ConfigLayerMeta = ConfigLayerMeta,
>(
async function resolveConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(
source: string,
options: LoadConfigOptions<T, MT>,
sourceOptions: SourceOptions<T, MT> = {},
Expand All @@ -356,18 +319,11 @@ async function resolveConfig<
const _merger = options.merger || defu;

// Download giget URIs and resolve to local path
const customProviderKeys = Object.keys(
sourceOptions.giget?.providers || {},
).map((key) => `${key}:`);
const customProviderKeys = Object.keys(sourceOptions.giget?.providers || {}).map((key) => `${key}:`);
const gigetPrefixes =
customProviderKeys.length > 0
? [...new Set([...customProviderKeys, ...GIGET_PREFIXES])]
: GIGET_PREFIXES;

if (
options.giget !== false &&
gigetPrefixes.some((prefix) => source.startsWith(prefix))
) {
customProviderKeys.length > 0 ? [...new Set([...customProviderKeys, ...GIGET_PREFIXES])] : GIGET_PREFIXES;

if (options.giget !== false && gigetPrefixes.some((prefix) => source.startsWith(prefix))) {
const { downloadTemplate } = await import("giget");
const { digest } = await import("ohash");

Expand Down Expand Up @@ -427,10 +383,7 @@ async function resolveConfig<

res.configFile =
tryResolve(resolve(cwd, source), options) ||
tryResolve(
resolve(cwd, ".config", source.replace(/\.config$/, "")),
options,
) ||
tryResolve(resolve(cwd, ".config", source.replace(/\.config$/, "")), options) ||
tryResolve(resolve(cwd, ".config", source), options) ||
source;

Expand All @@ -442,8 +395,7 @@ async function resolveConfig<

const configFileExt = extname(res.configFile!) || "";
if (configFileExt in ASYNC_LOADERS) {
const asyncLoader =
await ASYNC_LOADERS[configFileExt as keyof typeof ASYNC_LOADERS]();
const asyncLoader = await ASYNC_LOADERS[configFileExt as keyof typeof ASYNC_LOADERS]();
const contents = await readFile(res.configFile!, "utf8");
res.config = asyncLoader(contents);
} else {
Expand All @@ -452,9 +404,7 @@ async function resolveConfig<
})) as T;
}
if (typeof res.config === "function") {
res.config = await (
res.config as (ctx?: ConfigFunctionContext) => Promise<any>
)(options.context);
res.config = await (res.config as (ctx?: ConfigFunctionContext) => Promise<any>)(options.context);
}

// Extend env specific config
Expand Down
24 changes: 6 additions & 18 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,13 @@ export interface ResolvedConfig<
_configFile?: string;
}

export type ConfigSource =
| "overrides"
| "main"
| "rc"
| "packageJson"
| "defaultConfig";
export type ConfigSource = "overrides" | "main" | "rc" | "packageJson" | "defaultConfig";

export interface ConfigFunctionContext {
[key: string]: any;
}

export interface ResolvableConfigContext<
T extends UserInputConfig = UserInputConfig,
> {
export interface ResolvableConfigContext<T extends UserInputConfig = UserInputConfig> {
configs: Record<ConfigSource, T | null | undefined>;
rawConfigs: Record<ConfigSource, ResolvableConfig<T> | null | undefined>;
}
Expand Down Expand Up @@ -135,11 +128,7 @@ export interface LoadConfigOptions<
resolve?: (
id: string,
options: LoadConfigOptions<T, MT>,
) =>
| null
| undefined
| ResolvedConfig<T, MT>
| Promise<ResolvedConfig<T, MT> | undefined | null>;
) => null | undefined | ResolvedConfig<T, MT> | Promise<ResolvedConfig<T, MT> | undefined | null>;

jiti?: Jiti;
jitiOptions?: JitiOptions;
Expand All @@ -157,10 +146,9 @@ export interface LoadConfigOptions<
configFileRequired?: boolean;
}

export type DefineConfig<
T extends UserInputConfig = UserInputConfig,
MT extends ConfigLayerMeta = ConfigLayerMeta,
> = (input: InputConfig<T, MT>) => InputConfig<T, MT>;
export type DefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> = (
input: InputConfig<T, MT>,
) => InputConfig<T, MT>;

export function createDefineConfig<
T extends UserInputConfig = UserInputConfig,
Expand Down
37 changes: 8 additions & 29 deletions src/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,24 @@ const UPDATABLE_EXTS = [".js", ".ts", ".mjs", ".cjs", ".mts", ".cts"] as const;
/**
* @experimental Update a config file or create a new one.
*/
export async function updateConfig(
opts: UpdateConfigOptions,
): Promise<UpdateConfigResult> {
export async function updateConfig(opts: UpdateConfigOptions): Promise<UpdateConfigResult> {
const { parseModule } = await import("magicast");

// Try to find an existing config file
let configFile =
tryResolve(`./${opts.configFile}`, opts.cwd, SUPPORTED_EXTENSIONS) ||
tryResolve(
`./.config/${opts.configFile}`,
opts.cwd,
SUPPORTED_EXTENSIONS,
) ||
tryResolve(
`./.config/${opts.configFile.split(".")[0]}`,
opts.cwd,
SUPPORTED_EXTENSIONS,
);
tryResolve(`./.config/${opts.configFile}`, opts.cwd, SUPPORTED_EXTENSIONS) ||
tryResolve(`./.config/${opts.configFile.split(".")[0]}`, opts.cwd, SUPPORTED_EXTENSIONS);

// If not found
let created = false;
if (!configFile) {
configFile = join(
opts.cwd,
opts.configFile + (opts.createExtension || ".ts"),
);
const createResult =
(await opts.onCreate?.({ configFile: configFile })) ?? true;
configFile = join(opts.cwd, opts.configFile + (opts.createExtension || ".ts"));
const createResult = (await opts.onCreate?.({ configFile: configFile })) ?? true;
if (!createResult) {
throw new Error("Config file creation aborted.");
}
const content =
typeof createResult === "string" ? createResult : `export default {}\n`;
const content = typeof createResult === "string" ? createResult : `export default {}\n`;
await mkdir(dirname(configFile), { recursive: true });
await writeFile(configFile, content, "utf8");
created = true;
Expand All @@ -62,10 +47,7 @@ export async function updateConfig(
if (!defaultExport) {
throw new Error("Default export is missing in the config file!");
}
const configObj =
defaultExport.$type === "function-call"
? defaultExport.$args[0]
: defaultExport;
const configObj = defaultExport.$type === "function-call" ? defaultExport.$args[0] : defaultExport;

await opts.onUpdate?.(configObj);

Expand Down Expand Up @@ -99,10 +81,7 @@ export interface UpdateConfigResult {

type MaybePromise<T> = T | Promise<T>;

type MagicAstOptions = Exclude<
Parameters<(typeof import("magicast"))["parseModule"]>[1],
undefined
>;
type MagicAstOptions = Exclude<Parameters<(typeof import("magicast"))["parseModule"]>[1], undefined>;

export interface UpdateConfigOptions {
/**
Expand Down
Loading
Loading