import { Context } from "@sfajs/pipe"; import { HttpContext, isUndefined } from "@sfajs/core"; import path from "path"; import { Configuration, ConfigEnv } from "@sfajs/cli-common"; import { Inject } from "@sfajs/inject"; import { CommandService } from "./command.service"; import { FileService } from "./file.service"; import * as tsNode from "ts-node"; import _ from "lodash"; import { PluginInterfaceService } from "./plugin-interface.service"; export class ConfigService { @Context private readonly ctx!: HttpContext; @Inject private readonly fileService!: FileService; @Inject private readonly commandService!: CommandService; @Inject private readonly pluginInterfaceService!: PluginInterfaceService; #configFileName: string | undefined = undefined; private get configFileName() { if (this.#configFileName == undefined) { const optionsConfigName = this.commandService.getOptionVlaue("configName"); if (optionsConfigName) { this.#configFileName = optionsConfigName; } else { const exts = ["ts", "js", "json"]; const names = [ "sfa-cli.config", "sfacli.config", "sfa-cli", "sfacli", "sfa-cli-config", "sfacli-config", "sfa_cli.config", "sfacli_config", "sfa_cli_config", "sfa_cli", ]; const files: string[] = []; names.forEach((name) => { exts.forEach((ext) => { files.push(`${name}.${ext}`); }); }); this.#configFileName = this.fileService.existAny(files) ?? ""; } } return this.#configFileName; } private get configEnv(): ConfigEnv { return { mode: this.mode, command: this.ctx.command, }; } public get mode() { return this.getOptionOrConfigValue("mode", "mode", "production"); } #value: Configuration | undefined = undefined; public get value(): Configuration { return this.#value ?? {}; } public async init() { if (!this.#value) { this.#value = await this.loadConfig(); } } private async loadConfig(): Promise { const config: Configuration = {}; const cliConfigs = this.pluginInterfaceService.get("cliConfig"); for (let cliConfig of cliConfigs) { if (typeof cliConfig == "function") { cliConfig = cliConfig(this.configEnv); } _.merge(config, cliConfig); } _.merge(config, await this.getConfig()); return config; } private async getConfig(): Promise { const jsonConfig = this.commandService.getOptionVlaue("jsonConfig"); if (jsonConfig) { return JSON.parse(jsonConfig); } const funcConfig = this.commandService.getOptionVlaue("funcConfig"); if (funcConfig) { return new Function(`return ${funcConfig}`)()(this.configEnv); } const configFilePath = this.configFileName ? path.resolve(process.cwd(), this.configFileName) : undefined; if (configFilePath) { const config = await this.readConfigFile(configFilePath); if (config) { return config; } } return {}; } private async readConfigFile( configFilePath: string ): Promise { const jsJson = this.configFileName.toLowerCase().endsWith(".json"); if (jsJson) { return require(configFilePath); } const isTS = this.configFileName.toLowerCase().endsWith(".ts"); if (isTS) { const registerer = await this.registerTsNode(); registerer.enabled(true); try { return this.requireConfig(configFilePath); } finally { registerer.enabled(false); } } const isJS = this.configFileName.toLowerCase().endsWith(".js"); if (isJS) { return this.requireConfig(configFilePath); } } private async requireConfig(configFilePath: string) { // eslint-disable-next-line @typescript-eslint/no-var-requires const module = require(configFilePath); if (typeof module == "function") { return module(this.configEnv); } else if (module.default) { return module.default(this.configEnv); } else { return {}; } } private async registerTsNode() { return tsNode.register({ compilerOptions: { module: "CommonJS", }, moduleTypes: { "**": "cjs", }, }); } getConfigValue(paths: string[] | string): T | undefined; getConfigValue(paths: string[] | string, defaultVal: T): T; getConfigValue( paths: string[] | string, defaultVal?: T ): T | undefined { if (!Array.isArray(paths)) { paths = [paths]; } for (const property of paths) { const value = this.getDeepConfigValue(this.value, property); if (!isUndefined(value)) { return value; } } return defaultVal; } private getDeepConfigValue( obj: any, property: string ): T | undefined { if (!property || !obj) { return undefined; } if (obj[property] != undefined) { return obj[property]; } if (!property.includes(".")) { return undefined; } const firstFragment = property.replace(/\..*$/, ""); if (!obj[firstFragment]) { return undefined; } return this.getDeepConfigValue( obj[firstFragment], property.replace(/^.*?\./, "") ); } getOptionOrConfigValue( optionCommands: string[] | string, configPaths: string[] | string ): T | undefined; getOptionOrConfigValue( optionCommands: string[] | string, configPaths: string[] | string, defaultVal: T ): T; getOptionOrConfigValue( optionCommands: string[] | string, configPaths: string[] | string ): T | U | undefined; getOptionOrConfigValue( optionCommands: string[] | string, configPaths: string[] | string, defaultVal: T | U ): T | U; getOptionOrConfigValue( optionCommands: string[] | string, configPaths: string[] | string, defaultVal?: T | U ): T | U | undefined { if (!Array.isArray(optionCommands)) { optionCommands = [optionCommands]; } if (!Array.isArray(configPaths)) { configPaths = [configPaths]; } const optionValue = this.commandService.getOptionVlaue(optionCommands); if (!isUndefined(optionValue)) { return optionValue; } const configValue = this.getConfigValue(configPaths); if (!isUndefined(configValue)) { return configValue; } return defaultVal; } }