|
1 | | -import * as path from "path"; |
2 | | -import * as _ from "lodash"; |
| 1 | +import path from "path"; |
| 2 | +import _ from "lodash"; |
3 | 3 | import defaults from "./defaults"; |
4 | 4 | import { BrowserConfig } from "./browser-config"; |
5 | 5 | import parseOptions from "./options"; |
6 | 6 | import * as logger from "../utils/logger"; |
7 | | -import { ConfigInput, ConfigParsed } from "./types"; |
| 7 | +import { ConfigInput, ConfigInputData, ConfigParsed } from "./types"; |
8 | 8 | import { addUserAgentToArgs } from "./utils"; |
9 | 9 |
|
10 | 10 | export { TimeTravelMode } from "./types"; |
11 | 11 |
|
12 | 12 | export class Config { |
13 | | - configPath!: string; |
| 13 | + configPath?: string; |
14 | 14 |
|
15 | | - static create(config?: string | ConfigInput): Config { |
16 | | - return new Config(config); |
| 15 | + static async create(config?: string | ConfigInput): Promise<Config> { |
| 16 | + try { |
| 17 | + const { configPath, options } = await Config._resolve(config); |
| 18 | + |
| 19 | + await Config._prepareEnvironment(options); |
| 20 | + |
| 21 | + return new Config(options, configPath); |
| 22 | + } catch (e: unknown) { |
| 23 | + const error = new Error(`Got an error while trying to read config: ${(e as Error).message}`); |
| 24 | + error.stack = (e as Error).stack; |
| 25 | + error.cause = (e as Error).cause; |
| 26 | + |
| 27 | + throw error; |
| 28 | + } |
17 | 29 | } |
18 | 30 |
|
19 | | - static read(configPath: string): unknown { |
| 31 | + static async read(configPath: string): Promise<ConfigInputData> { |
20 | 32 | try { |
21 | 33 | // eslint-disable-next-line @typescript-eslint/no-var-requires |
22 | 34 | const configModule = require(path.resolve(process.cwd(), configPath)); |
| 35 | + const exported = (configModule.__esModule ? configModule.default : configModule) as ConfigInput; |
23 | 36 |
|
24 | | - return configModule.__esModule ? configModule.default : configModule; |
| 37 | + return await Config._resolveExportedConfig(exported); |
25 | 38 | } catch (e) { |
26 | 39 | logger.error(`Unable to read config from path ${configPath}`); |
27 | 40 | throw e; |
28 | 41 | } |
29 | 42 | } |
30 | 43 |
|
31 | | - constructor(config?: string | ConfigInput) { |
32 | | - let options: ConfigInput; |
| 44 | + private static async _resolve( |
| 45 | + config?: string | ConfigInput, |
| 46 | + ): Promise<{ configPath?: string; options: ConfigInputData }> { |
| 47 | + if (typeof config === "function") { |
| 48 | + return { options: await Config._resolveExportedConfig(config) }; |
| 49 | + } |
| 50 | + |
33 | 51 | if (_.isObjectLike(config)) { |
34 | | - options = config as ConfigInput; |
35 | | - } else if (typeof config === "string") { |
36 | | - this.configPath = config; |
37 | | - options = Config.read(config) as ConfigInput; |
38 | | - } else { |
39 | | - for (const configPath of defaults.configPaths) { |
40 | | - try { |
41 | | - const resolvedConfigPath = path.resolve(configPath); |
42 | | - require(resolvedConfigPath); |
43 | | - this.configPath = resolvedConfigPath; |
44 | | - |
45 | | - break; |
46 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
47 | | - } catch (err: any) { |
48 | | - if (err.code !== "MODULE_NOT_FOUND") { |
49 | | - throw err; |
50 | | - } |
| 52 | + return { options: config as ConfigInputData }; |
| 53 | + } |
| 54 | + |
| 55 | + if (typeof config === "string") { |
| 56 | + return { configPath: config, options: await Config.read(config) }; |
| 57 | + } |
| 58 | + |
| 59 | + const located = Config._locateConfigPath(); |
| 60 | + |
| 61 | + if (!located) { |
| 62 | + throw new Error(`Unable to read config from paths: ${defaults.configPaths.join(", ")}`); |
| 63 | + } |
| 64 | + |
| 65 | + return { configPath: located, options: await Config.read(located) }; |
| 66 | + } |
| 67 | + |
| 68 | + private static _locateConfigPath(): string | null { |
| 69 | + for (const configPath of defaults.configPaths) { |
| 70 | + try { |
| 71 | + const resolvedConfigPath = path.resolve(configPath); |
| 72 | + // eslint-disable-next-line @typescript-eslint/no-var-requires |
| 73 | + require(resolvedConfigPath); |
| 74 | + |
| 75 | + return resolvedConfigPath; |
| 76 | + } catch (err: unknown) { |
| 77 | + if ((err as { code?: string }).code !== "MODULE_NOT_FOUND") { |
| 78 | + throw err; |
51 | 79 | } |
52 | 80 | } |
| 81 | + } |
53 | 82 |
|
54 | | - if (!this.configPath) { |
55 | | - throw new Error(`Unable to read config from paths: ${defaults.configPaths.join(", ")}`); |
56 | | - } |
| 83 | + return null; |
| 84 | + } |
57 | 85 |
|
58 | | - options = Config.read(this.configPath) as ConfigInput; |
59 | | - } |
| 86 | + private static async _resolveExportedConfig(exported: ConfigInput): Promise<ConfigInputData> { |
| 87 | + const resolved = typeof exported === "function" ? await (exported as () => unknown)() : exported; |
| 88 | + |
| 89 | + return resolved as ConfigInputData; |
| 90 | + } |
60 | 91 |
|
| 92 | + private static async _prepareEnvironment(options: ConfigInputData): Promise<void> { |
61 | 93 | if (_.isFunction(options.prepareEnvironment)) { |
62 | | - options.prepareEnvironment(); |
| 94 | + await options.prepareEnvironment(); |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + constructor(options: ConfigInputData, configPath?: string) { |
| 99 | + if (configPath) { |
| 100 | + this.configPath = configPath; |
63 | 101 | } |
64 | 102 |
|
65 | 103 | const parsedOptions = parseOptions({ |
|
0 commit comments