import { log } from "builder-util" import { loadTsConfig } from "config-file-ts" import { parse as parseEnv } from "dotenv" import { DotenvParseInput, expand } from "dotenv-expand" import { promises as fs } from "fs" import { load } from "js-yaml" import { Lazy } from "lazy-val" import * as path from "path" import { resolveModule } from "../resolve" export interface ReadConfigResult { readonly result: T readonly configFile: string | null } async function readConfig(configFile: string, request: ReadConfigRequest): Promise> { const data = await fs.readFile(configFile, "utf8") let result: any if (configFile.endsWith(".json5") || configFile.endsWith(".json")) { result = require("json5").parse(data) } else if (configFile.endsWith(".js") || configFile.endsWith(".cjs") || configFile.endsWith(".mjs")) { const json = await orNullIfFileNotExist(fs.readFile(path.join(process.cwd(), "package.json"), "utf8")) const moduleType = json === null ? null : JSON.parse(json).type result = await resolveModule(moduleType, configFile) if (result.default != null) { result = result.default } if (typeof result === "function") { result = result(request) } result = await Promise.resolve(result) } else if (configFile.endsWith(".ts")) { // override logger temporarily to clean up console (config-file-ts does some internal logging that blogs up the default electron-builder log format) const consoleLogger = console.log console.log = (...args) => { log.debug({ args }, "executing config-file-ts") } result = loadTsConfig(configFile) console.log = consoleLogger if (typeof result === "function") { result = result(request) } result = await Promise.resolve(result) } else if (configFile.endsWith(".toml")) { result = require("toml").parse(data) } else { result = load(data) } return { result, configFile } } export async function findAndReadConfig(request: ReadConfigRequest): Promise | null> { const prefix = request.configFilename for (const configFile of [`${prefix}.yml`, `${prefix}.yaml`, `${prefix}.json`, `${prefix}.json5`, `${prefix}.toml`, `${prefix}.js`, `${prefix}.cjs`, `${prefix}.ts`]) { const data = await orNullIfFileNotExist(readConfig(path.join(request.projectDir, configFile), request)) if (data != null) { return data } } return null } export function orNullIfFileNotExist(promise: Promise): Promise { return orIfFileNotExist(promise, null) } export function orIfFileNotExist(promise: Promise, fallbackValue: T): Promise { return promise.catch(e => { if (e.code === "ENOENT" || e.code === "ENOTDIR") { return fallbackValue } throw e }) } export interface ReadConfigRequest { packageKey: string configFilename: string projectDir: string packageMetadata: Lazy | null> | null } export async function loadConfig(request: ReadConfigRequest): Promise | null> { let packageMetadata = request.packageMetadata == null ? null : await request.packageMetadata.value if (packageMetadata == null) { const json = await orNullIfFileNotExist(fs.readFile(path.join(request.projectDir, "package.json"), "utf8")) packageMetadata = json == null ? null : JSON.parse(json) } const data: T = packageMetadata == null ? null : packageMetadata[request.packageKey] return data == null ? findAndReadConfig(request) : { result: data, configFile: null } } export function getConfig(request: ReadConfigRequest, configPath?: string | null): Promise | null> { if (configPath == null) { return loadConfig(request) } else { return readConfig(path.resolve(request.projectDir, configPath), request) } } export async function loadParentConfig(request: ReadConfigRequest, spec: string): Promise> { let isFileSpec: boolean | undefined if (spec.startsWith("file:")) { spec = spec.substring("file:".length) isFileSpec = true } let parentConfig = await orNullIfFileNotExist(readConfig(path.resolve(request.projectDir, spec), request)) if (parentConfig == null && isFileSpec !== true) { let resolved: string | null = null try { resolved = require.resolve(spec) } catch (_e) { // ignore } if (resolved != null) { parentConfig = await readConfig(resolved, request) } } if (parentConfig == null) { throw new Error(`Cannot find parent config file: ${spec}`) } return parentConfig } export async function loadEnv(envFile: string) { const data = await orNullIfFileNotExist(fs.readFile(envFile, "utf8")) if (data == null) { return null } const parsed = parseEnv(data) log.info({ envFile }, "injecting environment") Object.entries(parsed).forEach(([key, value]) => { if (!Object.prototype.hasOwnProperty.call(process.env, key)) { process.env[key] = value } }) expand({ parsed }) return parsed }