import fs from 'fs' import path from 'path' import * as logger from './log' import { createEmitter, createEmitHook, createOnHook } from './createEmitter' import { setCurrentPrestaInstance, getCurrentPrestaInstance } from './currentPrestaInstance' import { Presta, Config, CLI, Callable } from './types' import { Env } from './constants' const defaultConfigFilepath = 'presta.config.js' function resolveAbsolutePaths( config: { files?: string | string[] output?: string assets?: string }, { cwd }: { cwd: string } ) { if (config.files) config.files = ([] as string[]).concat(config.files).map((p) => path.resolve(cwd, p)) if (config.output) config.output = path.resolve(cwd, config.output) if (config.assets) config.assets = path.resolve(cwd, config.assets) return config } /** * @private */ export function _clearCurrentConfig() { // @ts-ignore global.__presta__ = { pid: process.pid, cwd: process.cwd(), env: Env.PRODUCTION, } } /** * Fetch a config file. If one was specified by the user, let them know if * anything goes wrong. Outside watch mode, this should exit(1) if the user * provided a config and there was an error */ export function getConfigFile(filepath?: string, shouldExit: boolean = false) { const fp = path.resolve(filepath || defaultConfigFilepath) try { return require(fp) } catch (e) { const exists = fs.existsSync(fp) // config file exists, should log error, otherwise ignore missing file if (exists) { logger.error({ label: 'error', error: e as Error, }) // we're not in watch mode, exit build if (shouldExit) process.exit(1) } return {} } } /** * Creates a new instance _without_ any values provided by the config file. * This is used when the user deletes their config file. */ export async function removeConfigValues() { logger.debug({ label: 'debug', message: `config file values cleared`, }) return setCurrentPrestaInstance( await createConfig({ ...getCurrentPrestaInstance(), config: {}, }) ) } export async function createConfig({ cwd = process.cwd(), env = getCurrentPrestaInstance().env, config = {}, cli = {}, }: { cwd?: string env?: string config?: Partial cli?: Partial }) { config = resolveAbsolutePaths({ ...config }, { cwd }) // clone read-only obj cli = resolveAbsolutePaths({ ...cli }, { cwd }) // combined config, preference to CLI args const merged = { output: path.resolve(cwd, cli.output || config.output || 'build'), assets: path.resolve(cli.assets || config.assets || 'public'), files: cli.files && cli.files.length ? cli.files : config.files ? ([] as string[]).concat(config.files) : [], } const port = cli.port ? parseInt(cli.port) : config.port || 4000 const previous = getCurrentPrestaInstance() // only create once const emitter = previous.events || createEmitter() // deregister old events emitter.clear() // set instance const next: Presta = setCurrentPrestaInstance({ ...previous, ...merged, // overwrites every time env, cwd, port, debug: cli.debug || getCurrentPrestaInstance().debug, configFilepath: path.resolve(cli.config || defaultConfigFilepath), staticOutputDir: path.join(merged.output, 'static'), functionsOutputDir: path.join(merged.output, 'functions'), functionsManifest: path.join(merged.output, 'routes.json'), events: emitter, hooks: { emitPostBuild(props) { emitter.emit('postBuild', props) }, onPostBuild(cb) { return emitter.on('postBuild', cb) }, emitBuildFile(props) { emitter.emit('buildFile', props) }, onBuildFile(cb) { return emitter.on('buildFile', cb) }, emitBrowserRefresh() { emitter.emit('browserRefresh') }, onBrowserRefresh(cb) { return emitter.on('browserRefresh', cb) }, }, }) if (config.plugins) { await Promise.all( config.plugins.map((p) => { try { return p(getCurrentPrestaInstance) } catch (e) { logger.error({ label: 'error', error: e as Error, }) } }) ) } logger.debug({ label: 'debug', message: `config created ${JSON.stringify(next)}`, }) return next }