mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-08 02:07:56 -06:00
feat: revamp web build script in bun + typescript, delete old scripts
This commit is contained in:
74
build.sh
74
build.sh
@@ -1,74 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
release='false'
|
|
||||||
serve='false'
|
|
||||||
skip_emsdk='false'
|
|
||||||
clean='false'
|
|
||||||
|
|
||||||
print_usage() {
|
|
||||||
printf "Usage: -erdsc\n"
|
|
||||||
printf " -e: Skip EMSDK setup (GitHub workflow only)\n"
|
|
||||||
printf " -r: Build in release mode\n"
|
|
||||||
printf " -d: Build in debug mode\n"
|
|
||||||
printf " -s: Serve the WASM files once built\n"
|
|
||||||
printf " -c: Clean the target/dist directory\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
while getopts 'erdsc' flag; do
|
|
||||||
case "${flag}" in
|
|
||||||
e) skip_emsdk='true' ;;
|
|
||||||
r) release='true' ;;
|
|
||||||
d) release='false' ;; # doesn't actually do anything, but last flag wins
|
|
||||||
s) serve='true' ;;
|
|
||||||
c) clean='true' ;;
|
|
||||||
*)
|
|
||||||
print_usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$clean" = 'true' ]; then
|
|
||||||
echo "Cleaning target directory"
|
|
||||||
cargo clean
|
|
||||||
rm -rf ./dist/
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$skip_emsdk" = 'false' ]; then
|
|
||||||
echo "Activating Emscripten"
|
|
||||||
# SDL2-TTF requires 3.1.43, fails to build on latest
|
|
||||||
../emsdk/emsdk activate 3.1.43
|
|
||||||
source ../emsdk/emsdk_env.sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Building WASM with Emscripten"
|
|
||||||
build_type='debug'
|
|
||||||
if [ "$release" = 'true' ]; then
|
|
||||||
cargo build --target=wasm32-unknown-emscripten --release
|
|
||||||
build_type='release'
|
|
||||||
else
|
|
||||||
cargo build --target=wasm32-unknown-emscripten
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating CSS"
|
|
||||||
pnpx postcss-cli ./assets/site/styles.scss -o ./assets/site/build.css
|
|
||||||
|
|
||||||
echo "Copying WASM files"
|
|
||||||
mkdir -p dist
|
|
||||||
output_folder="target/wasm32-unknown-emscripten/$build_type"
|
|
||||||
|
|
||||||
cp assets/site/{build.css,favicon.ico,index.html} dist
|
|
||||||
cp $output_folder/pacman.{wasm,js} dist
|
|
||||||
if [ -f $output_folder/deps/pacman.data ]; then
|
|
||||||
cp $output_folder/deps/pacman.data dist
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f $output_folder/pacman.wasm.map ]; then
|
|
||||||
cp $output_folder/pacman.wasm.map dist
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$serve" = 'true' ]; then
|
|
||||||
echo "Serving WASM with Emscripten"
|
|
||||||
python3 -m http.server -d ./dist/ 8080
|
|
||||||
fi
|
|
||||||
201
build.ts
201
build.ts
@@ -1,201 +0,0 @@
|
|||||||
import { $ } from "bun";
|
|
||||||
|
|
||||||
// This is a bun script, run with `bun run build.ts`
|
|
||||||
|
|
||||||
import * as path from "path";
|
|
||||||
import * as fs from "fs/promises";
|
|
||||||
|
|
||||||
async function clean() {
|
|
||||||
console.log("Cleaning...");
|
|
||||||
await $`cargo clean`;
|
|
||||||
await $`rm -rf ./dist/`;
|
|
||||||
console.log("Cleaned...");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setupEmscripten() {
|
|
||||||
const emsdkDir = "./emsdk";
|
|
||||||
const emsdkExists = await fs
|
|
||||||
.access(emsdkDir)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false);
|
|
||||||
|
|
||||||
if (!emsdkExists) {
|
|
||||||
console.log("Cloning Emscripten SDK...");
|
|
||||||
await $`git clone https://github.com/emscripten-core/emsdk.git`;
|
|
||||||
} else {
|
|
||||||
console.log("Emscripten SDK already exists, skipping clone.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const emscriptenToolchainPath = path.join(emsdkDir, "upstream", "emscripten");
|
|
||||||
const toolchainInstalled = await fs
|
|
||||||
.access(emscriptenToolchainPath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false);
|
|
||||||
|
|
||||||
if (!toolchainInstalled) {
|
|
||||||
console.log("Installing Emscripten toolchain...");
|
|
||||||
await $`./emsdk/emsdk install 3.1.43`;
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
"Emscripten toolchain 3.1.43 already installed, skipping install."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Activating Emscripten...");
|
|
||||||
await $`./emsdk/emsdk activate 3.1.43`;
|
|
||||||
console.log("Emscripten activated.");
|
|
||||||
|
|
||||||
// Set EMSDK environment variable for subsequent commands
|
|
||||||
process.env.EMSDK = path.resolve(emsdkDir);
|
|
||||||
|
|
||||||
const emsdkPython = path.join(path.resolve(emsdkDir), "python");
|
|
||||||
const emsdkNode = path.join(path.resolve(emsdkDir), "node", "16.20.0_64bit"); // Adjust node version if needed
|
|
||||||
const emsdkBin = path.join(path.resolve(emsdkDir), "upstream", "emscripten");
|
|
||||||
process.env.PATH = `${emsdkPython}:${emsdkNode}:${emsdkBin}:${process.env.PATH}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildWeb(release: boolean) {
|
|
||||||
console.log("Building WASM with Emscripten...");
|
|
||||||
const rustcFlags = [
|
|
||||||
"-C",
|
|
||||||
"link-arg=--preload-file",
|
|
||||||
"-C",
|
|
||||||
"link-arg=assets",
|
|
||||||
].join(" ");
|
|
||||||
|
|
||||||
if (release) {
|
|
||||||
await $`env RUSTFLAGS=${rustcFlags} cargo build --target=wasm32-unknown-emscripten --release`;
|
|
||||||
} else {
|
|
||||||
await $`env RUSTFLAGS=${rustcFlags} cargo build --target=wasm32-unknown-emscripten`;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Generating CSS...");
|
|
||||||
await $`pnpx postcss-cli ./assets/site/styles.scss -o ./assets/site/build.css`;
|
|
||||||
|
|
||||||
console.log("Copying WASM files...");
|
|
||||||
const buildType = release ? "release" : "debug";
|
|
||||||
const outputFolder = `target/wasm32-unknown-emscripten/${buildType}`;
|
|
||||||
await $`mkdir -p dist`;
|
|
||||||
await $`cp assets/site/index.html dist`;
|
|
||||||
await $`cp assets/site/*.woff* dist`;
|
|
||||||
await $`cp assets/site/build.css dist`;
|
|
||||||
await $`cp assets/site/favicon.ico dist`;
|
|
||||||
await $`cp ${outputFolder}/pacman.wasm dist`;
|
|
||||||
await $`cp ${outputFolder}/pacman.js dist`;
|
|
||||||
|
|
||||||
// Check if .data file exists before copying
|
|
||||||
try {
|
|
||||||
await fs.access(`${outputFolder}/pacman.data`);
|
|
||||||
await $`cp ${outputFolder}/pacman.data dist`;
|
|
||||||
} catch (e) {
|
|
||||||
console.log("No pacman.data file found, skipping copy.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if .map file exists before copying
|
|
||||||
try {
|
|
||||||
await fs.access(`${outputFolder}/pacman.wasm.map`);
|
|
||||||
await $`cp ${outputFolder}/pacman.wasm.map dist`;
|
|
||||||
} catch (e) {
|
|
||||||
console.log("No pacman.wasm.map file found, skipping copy.");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("WASM files copied.");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function serve() {
|
|
||||||
console.log("Serving WASM with Emscripten...");
|
|
||||||
await $`python3 -m http.server -d ./dist/ 8080`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
|
|
||||||
let release = false;
|
|
||||||
let serveFiles = false;
|
|
||||||
let skipEmscriptenSetup = false;
|
|
||||||
let cleanProject = false;
|
|
||||||
let target = "web"; // Default target
|
|
||||||
|
|
||||||
for (const arg of args) {
|
|
||||||
switch (arg) {
|
|
||||||
case "-r":
|
|
||||||
release = true;
|
|
||||||
break;
|
|
||||||
case "-s":
|
|
||||||
serveFiles = true;
|
|
||||||
break;
|
|
||||||
case "-e":
|
|
||||||
skipEmscriptenSetup = true;
|
|
||||||
break;
|
|
||||||
case "-c":
|
|
||||||
cleanProject = true;
|
|
||||||
break;
|
|
||||||
case "--target=linux":
|
|
||||||
target = "linux";
|
|
||||||
break;
|
|
||||||
case "--target=windows":
|
|
||||||
target = "windows";
|
|
||||||
break;
|
|
||||||
case "--target=web":
|
|
||||||
target = "web";
|
|
||||||
break;
|
|
||||||
case "-h":
|
|
||||||
case "--help":
|
|
||||||
console.log(`
|
|
||||||
Usage: ts-node build.ts [options]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-r Build in release mode
|
|
||||||
-s Serve the WASM files once built (for web target)
|
|
||||||
-e Skip EMSDK setup (GitHub workflow only)
|
|
||||||
-c Clean the target/dist directory
|
|
||||||
--target=[web|linux|windows] Specify target platform (default: web)
|
|
||||||
-h, --help Show this help message
|
|
||||||
`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanProject) {
|
|
||||||
await clean();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!skipEmscriptenSetup && target === "web") {
|
|
||||||
await setupEmscripten();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
case "web":
|
|
||||||
await buildWeb(release);
|
|
||||||
if (serveFiles) {
|
|
||||||
await serve();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "linux":
|
|
||||||
console.log("Building for Linux...");
|
|
||||||
if (release) {
|
|
||||||
await $`cargo build --release`;
|
|
||||||
} else {
|
|
||||||
await $`cargo build`;
|
|
||||||
}
|
|
||||||
console.log("Linux build complete.");
|
|
||||||
break;
|
|
||||||
case "windows":
|
|
||||||
console.log("Building for Windows...");
|
|
||||||
if (release) {
|
|
||||||
await $`cargo build --release --target=x86_64-pc-windows-msvc`; // Assuming MSVC toolchain
|
|
||||||
} else {
|
|
||||||
await $`cargo build --target=x86_64-pc-windows-msvc`;
|
|
||||||
}
|
|
||||||
console.log("Windows build complete.");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error("Invalid target specified.");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
252
web.build.ts
Normal file
252
web.build.ts
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import { $ } from "bun";
|
||||||
|
import { existsSync, promises as fs } from "fs";
|
||||||
|
import { platform } from "os";
|
||||||
|
import { dirname, join, relative, resolve } from "path";
|
||||||
|
import { match, P } from "ts-pattern";
|
||||||
|
|
||||||
|
type Os =
|
||||||
|
| { type: "linux"; wsl: boolean }
|
||||||
|
| { type: "windows" }
|
||||||
|
| { type: "macos" };
|
||||||
|
|
||||||
|
const os: Os = match(platform())
|
||||||
|
.with("win32", () => ({ type: "windows" as const }))
|
||||||
|
.with("linux", () => ({
|
||||||
|
type: "linux" as const,
|
||||||
|
// We detect WSL by checking for the presence of the WSLInterop file.
|
||||||
|
// This is a semi-standard method of detecting WSL, which is more than workable for this already hacky script.
|
||||||
|
wsl: existsSync("/proc/sys/fs/binfmt_misc/WSLInterop"),
|
||||||
|
}))
|
||||||
|
.with("darwin", () => ({ type: "macos" as const }))
|
||||||
|
.otherwise(() => {
|
||||||
|
throw new Error(`Unsupported platform: ${platform()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
function log(msg: string) {
|
||||||
|
console.log(`[web.build] ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the application with Emscripten, generate the CSS, and copy the files into 'dist'.
|
||||||
|
*
|
||||||
|
* @param release - Whether to build in release mode.
|
||||||
|
* @param env - The environment variables to inject into build commands.
|
||||||
|
*/
|
||||||
|
async function build(release: boolean, env: Record<string, string>) {
|
||||||
|
log(
|
||||||
|
`Building for 'wasm32-unknown-emscripten' for ${
|
||||||
|
release ? "release" : "debug"
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
await $`cargo build --target=wasm32-unknown-emscripten ${
|
||||||
|
release ? "--release" : ""
|
||||||
|
}`.env(env);
|
||||||
|
|
||||||
|
log("Generating CSS");
|
||||||
|
await $`pnpx postcss-cli ./assets/site/styles.scss -o ./assets/site/build.css`;
|
||||||
|
|
||||||
|
const buildType = release ? "release" : "debug";
|
||||||
|
const siteFolder = resolve("assets/site");
|
||||||
|
const outputFolder = resolve(`target/wasm32-unknown-emscripten/${buildType}`);
|
||||||
|
const dist = resolve("dist");
|
||||||
|
|
||||||
|
// The files to copy into 'dist'
|
||||||
|
const files = [
|
||||||
|
...["index.html", "favicon.ico", "build.css"].map((file) => ({
|
||||||
|
src: join(siteFolder, file),
|
||||||
|
dest: join(dist, file),
|
||||||
|
optional: false,
|
||||||
|
})),
|
||||||
|
...["pacman.wasm", "pacman.js", "deps/pacman.data"].map((file) => ({
|
||||||
|
src: join(outputFolder, file),
|
||||||
|
dest: join(dist, file.split("/").pop() || file),
|
||||||
|
optional: false,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
src: join(outputFolder, "pacman.wasm.map"),
|
||||||
|
dest: join(dist, "pacman.wasm.map"),
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create required destination folders
|
||||||
|
await Promise.all(
|
||||||
|
// Get the dirname of files, remove duplicates
|
||||||
|
[...new Set(files.map(({ dest }) => dirname(dest)))]
|
||||||
|
// Create the folders
|
||||||
|
.map(async (dir) => {
|
||||||
|
// If the folder doesn't exist, create it
|
||||||
|
if (!(await fs.exists(dir))) {
|
||||||
|
log(`Creating folder ${dir}`);
|
||||||
|
await fs.mkdir(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Copy the files to the dist folder
|
||||||
|
log("Copying files into dist");
|
||||||
|
await Promise.all(
|
||||||
|
files.map(async ({ optional, src, dest }) => {
|
||||||
|
match({ optional, exists: await fs.exists(src) })
|
||||||
|
// If optional and doesn't exist, skip
|
||||||
|
.with({ optional: true, exists: false }, () => {
|
||||||
|
log(
|
||||||
|
`Optional file ${os.type === "windows" ? "\\" : "/"}${relative(
|
||||||
|
process.cwd(),
|
||||||
|
src
|
||||||
|
)} does not exist, skipping...`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
// If not optional and doesn't exist, throw an error
|
||||||
|
.with({ optional: false, exists: false }, () => {
|
||||||
|
throw new Error(`Required file ${src} does not exist`);
|
||||||
|
})
|
||||||
|
// Otherwise, copy the file
|
||||||
|
.otherwise(async () => await fs.copyFile(src, dest));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if the Emscripten SDK is activated for a Windows or *nix machine by looking for a .exe file and the equivalent file on Linux/macOS. Returns both results for handling.
|
||||||
|
* @param emsdkDir - The directory containing the Emscripten SDK.
|
||||||
|
* @returns A record of environment variables.
|
||||||
|
*/
|
||||||
|
async function checkEmsdkType(
|
||||||
|
emsdkDir: string
|
||||||
|
): Promise<{ windows: boolean; nix: boolean }> {
|
||||||
|
const binary = resolve(join(emsdkDir, "upstream", "bin", "clang"));
|
||||||
|
|
||||||
|
return {
|
||||||
|
windows: await fs.exists(binary + ".exe"),
|
||||||
|
nix: await fs.exists(binary),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the Emscripten SDK environment variables.
|
||||||
|
* Technically, this doesn't actaully activate the environment variables for the current shell,
|
||||||
|
* it just runs the environment sourcing script and returns the environment variables for future command invocations.
|
||||||
|
* @param emsdkDir - The directory containing the Emscripten SDK.
|
||||||
|
* @returns A record of environment variables.
|
||||||
|
*/
|
||||||
|
async function activateEmsdk(
|
||||||
|
emsdkDir: string
|
||||||
|
): Promise<{ vars: Record<string, string> } | { err: string }> {
|
||||||
|
// Determine the environment script to use based on the OS
|
||||||
|
const envScript = match(os)
|
||||||
|
.with({ type: "windows" }, () => join(emsdkDir, "emsdk_env.bat"))
|
||||||
|
.with({ type: P.union("linux", "macos") }, () =>
|
||||||
|
join(emsdkDir, "emsdk_env.sh")
|
||||||
|
)
|
||||||
|
.exhaustive();
|
||||||
|
|
||||||
|
// Run the environment script and capture the output
|
||||||
|
const { stdout, stderr, exitCode } = await match(os)
|
||||||
|
.with({ type: "windows" }, () =>
|
||||||
|
// run the script, ignore it's output ('>nul'), then print the environment variables ('set')
|
||||||
|
$`cmd /c "${envScript} >nul && set"`.quiet()
|
||||||
|
)
|
||||||
|
.with({ type: P.union("linux", "macos") }, () =>
|
||||||
|
// run the script with bash, ignore it's output ('> /dev/null'), then print the environment variables ('env')
|
||||||
|
$`bash -c "source '${envScript}' && env"`.quiet()
|
||||||
|
)
|
||||||
|
.exhaustive();
|
||||||
|
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
return { err: stderr.toString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the output into a record of environment variables
|
||||||
|
const vars = Object.fromEntries(
|
||||||
|
stdout
|
||||||
|
.toString()
|
||||||
|
.split(os.type === "windows" ? /\r?\n/ : "\n") // Split output into lines, handling Windows CRLF vs *nix LF
|
||||||
|
.map((line) => line.split("=", 2)) // Parse each line as KEY=VALUE (limit to 2 parts)
|
||||||
|
.filter(([k, v]) => k && v) // Keep only valid key-value pairs (both parts exist)
|
||||||
|
);
|
||||||
|
|
||||||
|
return { vars };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// Print the OS detected
|
||||||
|
log(
|
||||||
|
"OS Detected: " +
|
||||||
|
match(os)
|
||||||
|
.with({ type: "windows" }, () => "Windows")
|
||||||
|
.with({ type: "linux" }, ({ wsl: isWsl }) =>
|
||||||
|
isWsl ? "Linux (via WSL)" : "Linux"
|
||||||
|
)
|
||||||
|
.with({ type: "macos" }, () => "macOS")
|
||||||
|
.exhaustive()
|
||||||
|
);
|
||||||
|
|
||||||
|
const release = process.env.RELEASE !== "0";
|
||||||
|
const emsdkDir = resolve("./emsdk");
|
||||||
|
const vars = match(await activateEmsdk(emsdkDir)) // result handling
|
||||||
|
.with({ vars: P.select() }, (vars) => vars)
|
||||||
|
.with({ err: P.any }, ({ err }) => {
|
||||||
|
log("Error activating Emscripten SDK: " + err);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.exhaustive();
|
||||||
|
|
||||||
|
// Check if the Emscripten SDK is activated/installed properly for the current OS
|
||||||
|
match({
|
||||||
|
os: os,
|
||||||
|
...(await checkEmsdkType(emsdkDir)),
|
||||||
|
})
|
||||||
|
// If the Emscripten SDK is not activated/installed properly, exit with an error
|
||||||
|
.with(
|
||||||
|
{
|
||||||
|
nix: false,
|
||||||
|
windows: false,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
log(
|
||||||
|
"Emscripten SDK does not appear to be activated/installed properly."
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// If the Emscripten SDK is activated for Windows, but is currently running on a *nix OS, exit with an error
|
||||||
|
.with(
|
||||||
|
{
|
||||||
|
nix: false,
|
||||||
|
windows: true,
|
||||||
|
os: { type: P.not("windows") },
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
log(
|
||||||
|
"Emscripten SDK appears to be activated for Windows, but is currently running on a *nix OS."
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// If the Emscripten SDK is activated for *nix, but is currently running on a Windows OS, exit with an error
|
||||||
|
.with(
|
||||||
|
{
|
||||||
|
nix: true,
|
||||||
|
windows: false,
|
||||||
|
os: { type: "windows" },
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
log(
|
||||||
|
"Emscripten SDK appears to be activated for *nix, but is currently running on a Windows OS."
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build the application
|
||||||
|
await build(release, vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point.
|
||||||
|
*/
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("[web.build] Error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user