import { buildSync, BuildFailure, BuildOptions } from 'esbuild' import { existsSync, writeFileSync, unlinkSync } from 'fs' import { DistPath, ScriptBase, ScriptPath } from './paths' import { UserScriptMetaFull } from './types' import readMeta from './readmeta' import { format, Options } from 'prettier' import { CLIArgs } from './main' export default function ( name: string, watchCallback: (meta: UserScriptMetaFull, error: string | null) => void, PrettierConfig: Options, CLIArgs: CLIArgs ): [UserScriptMetaFull, string | null] { //read meta file let [metaJson, metaString] = readMeta(name) let pathDist = DistPath(name) let result = runEsbuild( { entryPoints: [ScriptPath(name).main], outfile: pathDist, target: 'esnext', platform: 'node', format: 'esm', bundle: true, minify: CLIArgs.minify, define: { UserScriptName: `'${metaJson.name}'`, UserScriptNamespace: `'${metaJson.namespace}'`, UserScriptVersion: `'${metaJson.version}'`, UserScriptDownloadURL: `'${metaJson.downloadURL}'`, UserScriptSupportURL: `'${metaJson.supportURL}'`, UserScriptHomepageURL: `'${metaJson.homepageURL}'`, }, }, result => { let error = runPostEsbuild( name, result, metaString, CLIArgs, PrettierConfig ) watchCallback(metaJson, error) }, CLIArgs ) let error = runPostEsbuild( name, result, metaString, CLIArgs, PrettierConfig ) return [metaJson, error] } function doErrorFile( pathError: string, pathOutFile: string, error: string | null ) { if (error !== null) { writeFileSync(pathError, `${new Date().toISOString()}\n\n${error}`) if (existsSync(pathOutFile)) { unlinkSync(pathOutFile) } } else if (existsSync(pathError)) { unlinkSync(pathError) } } interface RunEsbuildResult { content: string | null error: string | null errorRaw?: BuildFailure } function runEsbuild( opts: BuildOptions, watchCallback: (result: RunEsbuildResult) => void, CLIArgs: CLIArgs ): RunEsbuildResult { opts.write = false if (CLIArgs.watch) { opts.watch = { onRebuild(err, res) { if (err) { watchCallback({ content: null, error: (err as BuildFailure).message, errorRaw: err, }) } else if (res) { let content = '' if (res.outputFiles && res.outputFiles.length > 0) { content = res.outputFiles[0].text } if (content === '') { watchCallback({ content: null, error: 'No output', }) } watchCallback({ content, error: null, }) } else { watchCallback({ content: null, error: 'No result', }) } }, } } try { let res = buildSync(opts) let content = '' if (res.outputFiles && res.outputFiles.length > 0) { content = clearFilenameComments(res.outputFiles[0].text) } if (content === '') { return { content: null, error: 'No output', } } return { content, error: null, } } catch (err) { return { content: null, error: (err as BuildFailure).message, } } } function runPostEsbuild( name: string, result: RunEsbuildResult, metaString: string, CLIArgs: CLIArgs, PrettierConfig: Options ) { let error: string | null = null let path = ScriptPath(name) let pathDist = DistPath(name) 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 = format(content, PrettierConfig) } writeFileSync(pathDist, content) } else { console.error(name, 'No output') } doErrorFile(path.error, pathDist, error) return error } //remove all filename comments function clearFilenameComments(content: string): string { let regexp = new RegExp(`//\\s*${ScriptBase}/.*(?:\\n|$)`, 'g') return content.replace(regexp, '') }