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 ", "specify a build.json file", "build.json"], ["--watch", "have esbuild watch and rebuild"], ["--watch-pattern", "glob pattern for esbuild to watch file"], ["--log-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); } }