import { BuildFailure, BuildOptions, BuildResult, build } from 'esbuild' import { UserScriptMetaFull } from './types' import readMeta from './readmeta' import { format, resolveConfig } from 'prettier' import { AllPaths, CLIArgs } from './main' import { writeFile, stat, unlink } from 'fs/promises' export interface runBuildResult { meta: UserScriptMetaFull error: string | null } export default async function runBuild(name: string) { //read meta file let { meta, metaString } = readMeta(name) let paths = AllPaths.script(name) let result = await runEsbuild({ entryPoints: [paths.main], outfile: paths.dist, target: 'esnext', platform: 'node', format: 'esm', bundle: true, minify: CLIArgs.minify, define: { UserScriptName: `'${meta.name}'`, UserScriptNamespace: `'${meta.namespace}'`, UserScriptVersion: `'${meta.version}'`, UserScriptDownloadURL: `'${meta.downloadURL}'`, UserScriptSupportURL: `'${meta.supportURL}'`, UserScriptHomepageURL: `'${meta.homepageURL}'`, }, }) let error = await postBuild(name, result, metaString) return { meta, error, } } interface RunEsbuildResult { content: string | null error: string | null errorRaw?: BuildFailure } async function runEsbuild(opts: BuildOptions): Promise { opts.write = false try { let res = await build(opts) return getResult(null, res) } catch (err) { return getResult(err as BuildFailure, null) } } function getResult(error: BuildFailure | null, result: BuildResult | null) { if (error) { return { content: null, error: (error as BuildFailure).message, errorRaw: error, } } else if (result) { let content = '' if (result.outputFiles && result.outputFiles.length > 0) { content = result.outputFiles[0].text if (!CLIArgs.srccomment) content = clearFilenameComments(content) } if (content === '') { return { content: null, error: 'No output', } } return { content, error: null, } } else { return { content: null, error: 'No result', } } } function clearFilenameComments(content: string): string { let regexp = new RegExp(`//\\s*${AllPaths.base.in}/.*(?:\\n|$)`, 'g') return content.replace(regexp, '') } async function postBuild( name: string, result: RunEsbuildResult, metaString: string ) { let error: string | null = null let paths = AllPaths.script(name) let PrettierConfig = (await resolveConfig(paths.dir)) ?? {} if (result.error) { console.error(name, result.errorRaw || result.error) error = result.error } else if (result.content) { let content = metaString + result.content if (CLIArgs.prettier) { content = await format(content, { ...PrettierConfig, parser: 'babel', }) } await writeFile(paths.dist, content) } else { console.error(name, 'No output') } await doErrorFile(name, error) return error } async function doErrorFile(name: string, error: string | null) { let paths = AllPaths.script(name) let content = `${new Date().toISOString()}\n\n${error}` if (error !== null) { await writeFile(paths.error, content) if (await existsFile(paths.dist)) { await unlink(paths.dist) } } else if (await existsFile(paths.error)) { await unlink(paths.error) } } function existsFile(path: string): Promise { return new Promise(resolve => { stat(path) .then(() => resolve(true)) .catch(() => resolve(false)) }) }