presta
Version:
Hyper minimal framework for the modern web.
170 lines (150 loc) • 4.39 kB
text/typescript
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<Config>
cli?: Partial<CLI>
}) {
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
}