import { promises as fs } from "fs"; import * as chokidar from "chokidar"; import { make_notifier } from "./notifier.js"; import getPort from "get-port"; import { resolve } from "node:path"; import _locreq from "locreq"; import { hasShape, predicates } from "@sealcode/ts-predicates"; import { FontsBuilder } from "./builders/fonts-builder.js"; import { BackendTSBuilder } from "./builders/backend-ts-builder.js"; import { FrontendTSBuilder } from "./builders/frontend-ts-builder.js"; import { CSSBuilder } from "./builders/css-builder.js"; import { ColorsBuilder } from "./builders/colors-builder.js"; const target_locreq = _locreq(process.cwd()); async function get_notifier_port() { return getPort({ port: 4000 }); } async function write_notifier_config(watch: boolean, port?: number) { const config = { watch } as Record; if (port) { config.port = port; } await fs.writeFile( target_locreq.resolve("public/dist/notifier.json"), JSON.stringify(config) ); } const package_json_shape = { sealgen: predicates.maybe( predicates.shape({ styleDirs: predicates.maybe(predicates.array(predicates.string)), controllerDirs: predicates.maybe( predicates.array(predicates.string) ), copyToPublic: predicates.maybe( predicates.array( predicates.shape({ from: predicates.string, to: predicates.string, }) ) ), }) ), }; async function build(watch: boolean): Promise { const project_dir = target_locreq.resolve(""); const package_json = JSON.parse( await fs.readFile(resolve(project_dir, "package.json"), "utf-8") ) as Record; if (!hasShape(package_json_shape, package_json)) { throw new Error("Misshaped package.json props"); } const style_dirs = package_json.sealgen?.styleDirs || []; const controller_dirs = package_json.sealgen?.controllerDirs || []; const copy_to_public = package_json.sealgen?.copyToPublic || []; await Promise.all( copy_to_public.map(async ({ from, to }) => { try { await fs.stat(resolve(project_dir, "public", to)); } catch (_e) { await fs.mkdir(resolve(project_dir, "public", to, "../"), { recursive: true, }); await fs.symlink( resolve(project_dir, from), resolve(project_dir, "public", to) ); } }) ); const fonts_builder = new FontsBuilder(project_dir, style_dirs); const backend_ts_builder = new BackendTSBuilder(project_dir, style_dirs); const frontend_ts_builder = new FrontendTSBuilder( project_dir, style_dirs, controller_dirs ); const css_builder = new CSSBuilder(project_dir, style_dirs); const colors_builder = new ColorsBuilder(project_dir, style_dirs); const builders = [ fonts_builder, backend_ts_builder, colors_builder, css_builder, frontend_ts_builder, ]; await Promise.all(builders.map((b) => b.build())); if (watch) { const watcher = chokidar.watch(["src", ...controller_dirs], { ignoreInitial: true, }); const port = await get_notifier_port(); const notifier = make_notifier(port, 8080); // TODO: parse the app config to get the actual port await write_notifier_config(watch, port); watcher.on("all", (_, file_path) => { console.info("Detected a change!", file_path); builders.forEach((builder) => { const owns_a_file = builder.ownsFile(file_path); if (owns_a_file) { void builder.build(notifier); } }); }); } else { await write_notifier_config(watch); builders.forEach((builder) => { void builder.dispose(); }); } } export async function buildProject({ watch, }: { watch: boolean; }): Promise { try { await build(watch); } catch (e) { console.error("CAUGHT!"); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access console.error(e.message); if (!watch) { process.exit(1); } } }