import fs from "fs/promises" ;
import { mkdir _to , exec _i } from "../lib/builderator.js" ;
import glob from "fast-glob" ;
import esbuild from "esbuild" ;
import sveltePlugin from "esbuild-svelte" ;
import path from 'path' ;
import { main as build _icons } from "./icons.js" ;
import { io } from "socket.io-client" ;
export const description = "Builds the Svelte app specified by --input and places the bundle in --output."
export const options = [
[ "--config <file>" , "specify a build.json file" , "build.json" ] ,
[ "--watch" , "have esbuild watch and rebuild" ] ,
[ "--watch-pattern" , "glob pattern for esbuild to watch file" ] ,
[ "--log-level <level>" , "esbuild log level" ] ,
[ "--legal-comments" , "how to handle copyright comments" ] ,
] ;
const CWD = process . cwd ( ) ;
const socket = io ( "ws://127.0.0.1:5001" ) ;
const dollarImport = ( ) => ( {
name : "dollarImport" ,
setup ( build ) {
build . onResolve ( { filter : /.*/ } , ( args ) => {
if ( args . path . startsWith ( "$" ) ) {
args . path = path . join ( CWD , args . path . slice ( 1 ) ) ;
return {
path : args . path ,
external : false ,
}
} else {
return { } ;
}
} )
}
} )
const buildIcons = ( production ) => ( {
name : "buildIcons" ,
setup ( build ) {
build . onStart ( async ( ) => {
const config = {
allIcons : "./static/icons/*.svg" ,
spriteSheet : "./public/icon-sprites.svg" ,
iconsIndex : "./public/icons/index.json" ,
dontExit : true
}
if ( production ) {
config . prodList = "./static/prod_icons.json" ;
}
await build _icons ( config ) ;
} ) ;
}
} )
const syncContent = ( watch _pattern ) => ( {
name : "syncContent" ,
setup ( build ) {
build . onResolve ( { filter : /.*main\.js/ } , ( args ) => {
// when there's no watch pattern we don't tell esbuild to watch
if ( watch _pattern ) {
const same _path = path . join ( CWD , args . path ) ;
const watch _files = glob . sync ( path . join ( CWD , watch _pattern ) ) ;
return {
path : same _path ,
watchFiles : watch _files
}
} else {
return undefined ;
}
} ) ;
build . onEnd ( async ( result ) => {
socket . emit ( "/reloader/notify" , { } ) ;
if ( result . errors . length > 0 ) {
console . error ( ` Build ended with ${ result . errors . length } errors ` ) ;
} else {
if ( process . platform === "win32" ) {
try {
exec _i ( "robocopy static\\ public\\ /e /NFL /NDL /NJH /NJS /nc /ns /np" ) ;
} catch ( error ) {
if ( error . status === 16 ) {
console . error ( error ) ;
}
}
} else {
exec _i ( "rsync -a static/ public/" ) ;
}
}
} ) ;
}
} )
const saveMetaFile = ( build _meta , error _file ) => ( {
name : "saveMetaFile" ,
setup ( build ) {
build . onEnd ( async ( result ) => {
// when there's an error esbuild doesn't include the metafile portion
if ( result . metafile ) {
mkdir _to ( build _meta ) ;
await fs . writeFile ( build _meta , JSON . stringify ( result . metafile , null , 4 ) ) ;
}
mkdir _to ( error _file ) ;
await fs . writeFile ( error _file , JSON . stringify ( result , null , 4 ) ) ;
} ) ;
}
} )
const devMode = ( ) => ( {
name : 'devMode' ,
setup ( build ) {
const options = build . initialOptions ;
options . define = options . define || { } ;
// esbuild 0.16 requires this to be a string, but changes it to code so this will be an actual boolean type in the code
options . define [ 'process.env.DANGER_ADMIN' ] = options . minify ? "0" : "1" ;
}
} )
export const run _build = async ( config , opts ) => {
let build _meta ;
let error _file ;
// assign the opts to the config
Object . assign ( config , opts ) ;
// tack on the plugins list for esbuild
config . plugins = [
devMode ( ) ,
dollarImport ( ) ,
sveltePlugin ( { compilerOptions : { css : true } } ) ,
buildIcons ( config . prod ) ,
syncContent ( config . watchPattern ) ,
] ;
// make the directories if they don't exist
mkdir _to ( config . outfile ) ;
// HACK: esbuild is too strict about stray params so remove
delete config . prod ;
delete config . watchPattern ;
// fix up the metafile since esbuild is weird about it
if ( typeof config . metafile === "string" ) {
build _meta = config . metafile ;
error _file = config . errorFile ;
config . metafile = true ;
delete config . errorFile ;
}
// add the plugin that saves the build results file if requested
if ( build _meta ) {
config . plugins . push ( saveMetaFile ( build _meta , error _file ) ) ;
}
// when using JS to build, it returns the Object rather than write JSON out
let result ;
try {
result = await esbuild . build ( config ) ;
} catch ( error ) {
// in this case, there is no result because of an error
result = error ;
}
return result . errors . length ;
}
export const main = async ( opts ) => {
const config = JSON . parse ( await fs . readFile ( opts . config ) ) ;
let error _count = 0 ;
// remove this since esbuild doesn't support it
delete opts . config ;
for ( let i = 0 ; i < config . length ; i ++ ) {
const build = config [ i ] ;
// BUG: I'd like this to be not-sequential but that's too hard to make reliable right now
error _count += await run _build ( build , opts ) ;
}
if ( error _count > 0 ) {
console . error ( ` Build finished with ${ error _count } errors. ` ) ;
}
if ( ! opts . watch ) {
process . exit ( error _count > 0 ? 1 : 0 ) ;
}
}