mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-09 16:12:17 +00:00
479 lines
13 KiB
JavaScript
479 lines
13 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Build script for /tg/station 13 codebase.
|
|
*
|
|
* This script uses Juke Build, read the docs here:
|
|
* https://github.com/stylemistake/juke-build
|
|
*/
|
|
|
|
import Bun from "bun";
|
|
import fs from "node:fs";
|
|
import Juke from "./juke/index.js";
|
|
import { bun } from "./lib/bun";
|
|
import { DreamDaemon, DreamMaker, NamedVersionFile } from "./lib/byond";
|
|
import { downloadFile } from "./lib/download";
|
|
import { formatDeps } from "./lib/helpers";
|
|
import { prependDefines } from "./lib/tgs";
|
|
|
|
export const TGS_MODE = process.env.CBT_BUILD_MODE === "TGS";
|
|
|
|
export const DME_NAME = "vorestation";
|
|
|
|
Juke.chdir("../..", import.meta.url);
|
|
|
|
const dependencies: Record<string, any> = await Bun.file("dependencies.sh")
|
|
.text()
|
|
.then(formatDeps)
|
|
.catch((err) => {
|
|
Juke.logger.error(
|
|
"Failed to read dependencies.sh, please ensure it exists and is formatted correctly.",
|
|
);
|
|
Juke.logger.error(err);
|
|
throw new Juke.ExitCode(1);
|
|
});
|
|
|
|
// Canonical path for the cutter exe at this moment
|
|
function getCutterPath() {
|
|
const ver = dependencies.CUTTER_VERSION;
|
|
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
const file_ver = ver.split(".").join("-");
|
|
|
|
return `tools/icon_cutter/cache/hypnagogic${file_ver}${suffix}`;
|
|
}
|
|
|
|
const cutter_path = getCutterPath();
|
|
|
|
export const DefineParameter = new Juke.Parameter({
|
|
type: "string[]",
|
|
alias: "D",
|
|
});
|
|
|
|
export const PortParameter = new Juke.Parameter({
|
|
type: "string",
|
|
alias: "p",
|
|
});
|
|
|
|
export const DmVersionParameter = new Juke.Parameter({
|
|
type: "string",
|
|
});
|
|
|
|
export const CiParameter = new Juke.Parameter({ type: "boolean" });
|
|
|
|
export const ForceRecutParameter = new Juke.Parameter({
|
|
type: "boolean",
|
|
name: "force-recut",
|
|
});
|
|
|
|
export const SkipIconCutter = new Juke.Parameter({
|
|
type: "boolean",
|
|
name: "skip-icon-cutter",
|
|
});
|
|
|
|
export const WarningParameter = new Juke.Parameter({
|
|
type: "string[]",
|
|
alias: "W",
|
|
});
|
|
|
|
export const NoWarningParameter = new Juke.Parameter({
|
|
type: "string[]",
|
|
alias: "I",
|
|
});
|
|
|
|
export const CutterTarget = new Juke.Target({
|
|
onlyWhen: () => {
|
|
const files = Juke.glob(cutter_path);
|
|
return files.length === 0;
|
|
},
|
|
executes: async () => {
|
|
const repo = dependencies.CUTTER_REPO;
|
|
const ver = dependencies.CUTTER_VERSION;
|
|
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
const download_from = `https://github.com/${repo}/releases/download/${ver}/hypnagogic${suffix}`;
|
|
await downloadFile(download_from, cutter_path);
|
|
if (process.platform !== "win32") {
|
|
await Juke.exec("chmod", ["+x", cutter_path]);
|
|
}
|
|
},
|
|
});
|
|
|
|
export const IconCutterTarget = new Juke.Target({
|
|
parameters: [ForceRecutParameter],
|
|
dependsOn: () => [CutterTarget],
|
|
inputs: () => {
|
|
const standard_inputs = [
|
|
`icons/**/*.png.toml`,
|
|
`icons/**/*.dmi.toml`,
|
|
`cutter_templates/**/*.toml`,
|
|
cutter_path,
|
|
];
|
|
// Alright we're gonna search out any existing toml files and convert
|
|
// them to their matching .dmi or .png file
|
|
const existing_configs = [
|
|
...Juke.glob(`icons/**/*.png.toml`),
|
|
...Juke.glob(`icons/**/*.dmi.toml`),
|
|
];
|
|
return [
|
|
...standard_inputs,
|
|
...existing_configs.map((file) => file.replace(".toml", "")),
|
|
];
|
|
},
|
|
outputs: ({ get }) => {
|
|
if (get(ForceRecutParameter)) return [];
|
|
const folders = [
|
|
...Juke.glob(`icons/**/*.png.toml`),
|
|
...Juke.glob(`icons/**/*.dmi.toml`),
|
|
];
|
|
return folders
|
|
.map((file) => file.replace(`.png.toml`, ".dmi"))
|
|
.map((file) => file.replace(`.dmi.toml`, ".png"));
|
|
},
|
|
executes: async () => {
|
|
await Juke.exec(cutter_path, [
|
|
"--dont-wait",
|
|
"--templates",
|
|
"cutter_templates",
|
|
"icons",
|
|
]);
|
|
},
|
|
});
|
|
|
|
export const DmMapsIncludeTarget = new Juke.Target({
|
|
executes: async () => {
|
|
const folders = [
|
|
...Juke.glob("_maps/map_files/**/modular_pieces/*.dmm"),
|
|
...Juke.glob("_maps/RandomRuins/**/*.dmm"),
|
|
...Juke.glob("_maps/RandomZLevels/**/*.dmm"),
|
|
...Juke.glob("_maps/shuttles/**/*.dmm"),
|
|
...Juke.glob("_maps/templates/**/*.dmm"),
|
|
];
|
|
const content =
|
|
folders
|
|
.map((file) => file.replace("_maps/", ""))
|
|
.map((file) => `#include "${file}"`)
|
|
.join("\n") + "\n";
|
|
fs.writeFileSync("_maps/templates.dm", content);
|
|
},
|
|
});
|
|
|
|
export const DmTarget = new Juke.Target({
|
|
parameters: [
|
|
DefineParameter,
|
|
DmVersionParameter,
|
|
WarningParameter,
|
|
NoWarningParameter,
|
|
SkipIconCutter,
|
|
],
|
|
dependsOn: ({ get }) => [
|
|
get(DefineParameter).includes("ALL_MAPS") && DmMapsIncludeTarget,
|
|
!get(SkipIconCutter) && IconCutterTarget,
|
|
],
|
|
inputs: [
|
|
"_maps/map_files/generic/**",
|
|
"maps/**/*.dm",
|
|
"code/**",
|
|
"html/**",
|
|
"icons/**",
|
|
"interface/**",
|
|
"sound/**",
|
|
"tgui/public/tgui.html",
|
|
`${DME_NAME}.dme`,
|
|
NamedVersionFile,
|
|
],
|
|
outputs: ({ get }) => {
|
|
if (get(DmVersionParameter)) {
|
|
return []; // Always rebuild when dm version is provided
|
|
}
|
|
return [`${DME_NAME}.dmb`, `${DME_NAME}.rsc`];
|
|
},
|
|
executes: async ({ get }) => {
|
|
await DreamMaker(`${DME_NAME}.dme`, {
|
|
defines: ["CBT", ...get(DefineParameter)],
|
|
warningsAsErrors: get(WarningParameter).includes("error"),
|
|
ignoreWarningCodes: get(NoWarningParameter),
|
|
namedDmVersion: get(DmVersionParameter),
|
|
});
|
|
},
|
|
});
|
|
|
|
export const DmTestTarget = new Juke.Target({
|
|
parameters: [
|
|
DefineParameter,
|
|
DmVersionParameter,
|
|
WarningParameter,
|
|
NoWarningParameter,
|
|
],
|
|
dependsOn: ({ get }) => [
|
|
get(DefineParameter).includes("ALL_MAPS") && DmMapsIncludeTarget,
|
|
IconCutterTarget,
|
|
],
|
|
executes: async ({ get }) => {
|
|
fs.copyFileSync(`${DME_NAME}.dme`, `${DME_NAME}.test.dme`);
|
|
await DreamMaker(`${DME_NAME}.test.dme`, {
|
|
defines: ["CBT", "CIBUILDING", ...get(DefineParameter)],
|
|
warningsAsErrors: get(WarningParameter).includes("error"),
|
|
ignoreWarningCodes: get(NoWarningParameter),
|
|
namedDmVersion: get(DmVersionParameter),
|
|
});
|
|
Juke.rm("data/logs/ci", { recursive: true });
|
|
const options = {
|
|
dmbFile: `${DME_NAME}.test.dmb`,
|
|
namedDmVersion: get(DmVersionParameter),
|
|
};
|
|
await DreamDaemon(
|
|
options,
|
|
"-close",
|
|
"-trusted",
|
|
"-verbose",
|
|
"-params",
|
|
"log-directory=ci",
|
|
);
|
|
Juke.rm("*.test.*");
|
|
try {
|
|
const cleanRun = fs.readFileSync("data/logs/ci/clean_run.lk", "utf-8");
|
|
console.log(cleanRun);
|
|
} catch (err) {
|
|
Juke.logger.error("Test run was not clean, exiting");
|
|
throw new Juke.ExitCode(1);
|
|
}
|
|
},
|
|
});
|
|
|
|
export const AutowikiTarget = new Juke.Target({
|
|
parameters: [
|
|
DefineParameter,
|
|
DmVersionParameter,
|
|
WarningParameter,
|
|
NoWarningParameter,
|
|
],
|
|
dependsOn: ({ get }) => [
|
|
get(DefineParameter).includes("ALL_MAPS") && DmMapsIncludeTarget,
|
|
IconCutterTarget,
|
|
],
|
|
outputs: ["data/autowiki_edits.txt"],
|
|
executes: async ({ get }) => {
|
|
fs.copyFileSync(`${DME_NAME}.dme`, `${DME_NAME}.test.dme`);
|
|
await DreamMaker(`${DME_NAME}.test.dme`, {
|
|
defines: ["CBT", "AUTOWIKI", ...get(DefineParameter)],
|
|
warningsAsErrors: get(WarningParameter).includes("error"),
|
|
ignoreWarningCodes: get(NoWarningParameter),
|
|
namedDmVersion: get(DmVersionParameter),
|
|
});
|
|
Juke.rm("data/autowiki_edits.txt");
|
|
Juke.rm("data/autowiki_files", { recursive: true });
|
|
Juke.rm("data/logs/ci", { recursive: true });
|
|
|
|
const options = {
|
|
dmbFile: `${DME_NAME}.test.dmb`,
|
|
namedDmVersion: get(DmVersionParameter),
|
|
};
|
|
await DreamDaemon(
|
|
options,
|
|
"-close",
|
|
"-trusted",
|
|
"-verbose",
|
|
"-params",
|
|
"log-directory=ci",
|
|
);
|
|
Juke.rm("*.test.*");
|
|
if (!fs.existsSync("data/autowiki_edits.txt")) {
|
|
Juke.logger.error("Autowiki did not generate an output, exiting");
|
|
throw new Juke.ExitCode(1);
|
|
}
|
|
},
|
|
});
|
|
|
|
export const BunTarget = new Juke.Target({
|
|
parameters: [CiParameter],
|
|
inputs: ["tgui/**/package.json"],
|
|
executes: () => {
|
|
return bun("install", "--frozen-lockfile", "--ignore-scripts");
|
|
},
|
|
});
|
|
|
|
export const TgFontTarget = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
inputs: [
|
|
"tgui/packages/tgfont/**/*.+(js|mjs|svg)",
|
|
"tgui/packages/tgfont/package.json",
|
|
],
|
|
outputs: [
|
|
"tgui/packages/tgfont/dist/tgfont.css",
|
|
"tgui/packages/tgfont/dist/tgfont.woff2",
|
|
],
|
|
executes: async () => {
|
|
await bun("tgfont:build");
|
|
fs.mkdirSync("tgui/packages/tgfont/static", { recursive: true });
|
|
fs.copyFileSync(
|
|
"tgui/packages/tgfont/dist/tgfont.css",
|
|
"tgui/packages/tgfont/static/tgfont.css",
|
|
);
|
|
fs.copyFileSync(
|
|
"tgui/packages/tgfont/dist/tgfont.woff2",
|
|
"tgui/packages/tgfont/static/tgfont.woff2",
|
|
);
|
|
},
|
|
});
|
|
|
|
export const TguiTarget = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
inputs: [
|
|
"tgui/rspack.config.ts",
|
|
"tgui/**/package.json",
|
|
"tgui/packages/**/*.+(js|cjs|ts|tsx|jsx|scss)",
|
|
],
|
|
outputs: [
|
|
"tgui/public/tgui.bundle.css",
|
|
"tgui/public/tgui.bundle.js",
|
|
"tgui/public/tgui-panel.bundle.css",
|
|
"tgui/public/tgui-panel.bundle.js",
|
|
"tgui/public/tgui-say.bundle.css",
|
|
"tgui/public/tgui-say.bundle.js",
|
|
],
|
|
executes: () => bun("tgui:build"),
|
|
});
|
|
|
|
export const TguiEslintTarget = new Juke.Target({
|
|
parameters: [CiParameter],
|
|
dependsOn: [BunTarget],
|
|
executes: ({ get }) => bun("tgui:lint", !get(CiParameter) && "--fix"),
|
|
});
|
|
|
|
export const TguiPrettierTarget = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
executes: () => bun("tgui:prettier"),
|
|
});
|
|
|
|
export const TguiSonarTarget = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
executes: () => bun("tgui:sonar"),
|
|
});
|
|
|
|
export const TguiTscTarget = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
executes: () => bun("tgui:tsc"),
|
|
});
|
|
|
|
export const TguiTestTarget = new Juke.Target({
|
|
parameters: [CiParameter],
|
|
dependsOn: [BunTarget],
|
|
executes: () => bun("tgui:test"),
|
|
});
|
|
|
|
export const TguiLintTarget = new Juke.Target({
|
|
dependsOn: [BunTarget, TguiPrettierTarget, TguiEslintTarget, TguiTscTarget],
|
|
});
|
|
|
|
export const TguiDevTarget = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
executes: ({ args }) => bun("tgui:dev", ...args),
|
|
});
|
|
|
|
export const TguiAnalyzeTarget = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
executes: () => bun("tgui:analyze"),
|
|
});
|
|
|
|
export const TguiBenchTarget = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
executes: () => bun("tgui:bench"),
|
|
});
|
|
|
|
export const TguiPrettierFix = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
executes: () => bun("tgui:prettier-fix"),
|
|
});
|
|
|
|
export const TguiEslintFix = new Juke.Target({
|
|
dependsOn: [BunTarget],
|
|
executes: () => bun("tgui:eslint-fix"),
|
|
});
|
|
|
|
export const TguiFix = new Juke.Target({
|
|
dependsOn: [TguiPrettierFix, TguiEslintFix],
|
|
});
|
|
|
|
export const TestTarget = new Juke.Target({
|
|
dependsOn: [DmTestTarget, TguiTestTarget],
|
|
});
|
|
|
|
export const LintTarget = new Juke.Target({
|
|
dependsOn: [TguiLintTarget],
|
|
});
|
|
|
|
export const BuildTarget = new Juke.Target({
|
|
dependsOn: [TguiTarget, DmTarget],
|
|
});
|
|
|
|
export const ServerTarget = new Juke.Target({
|
|
parameters: [DmVersionParameter, PortParameter],
|
|
dependsOn: [BuildTarget],
|
|
executes: async ({ get }) => {
|
|
const port = get(PortParameter) || "1337";
|
|
const options = {
|
|
dmbFile: `${DME_NAME}.dmb`,
|
|
namedDmVersion: get(DmVersionParameter),
|
|
};
|
|
await DreamDaemon(options, port, "-trusted -invisible");
|
|
},
|
|
});
|
|
|
|
export const AllTarget = new Juke.Target({
|
|
dependsOn: [TestTarget, LintTarget, BuildTarget],
|
|
});
|
|
|
|
export const TguiCleanTarget = new Juke.Target({
|
|
executes: async () => {
|
|
Juke.rm("tgui/public/.tmp", { recursive: true });
|
|
Juke.rm("tgui/public/*.map");
|
|
Juke.rm("tgui/public/*.{chunk,bundle,hot-update}.*");
|
|
Juke.rm("tgui/packages/tgfont/dist", { recursive: true });
|
|
Juke.rm("tgui/node_modules", { recursive: true });
|
|
},
|
|
});
|
|
|
|
export const CleanTarget = new Juke.Target({
|
|
dependsOn: [TguiCleanTarget],
|
|
executes: async () => {
|
|
Juke.rm("*.{dmb,rsc}");
|
|
Juke.rm("_maps/templates.dm");
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Removes more junk at the expense of much slower initial builds.
|
|
*/
|
|
export const CleanAllTarget = new Juke.Target({
|
|
dependsOn: [CleanTarget],
|
|
executes: async () => {
|
|
Juke.logger.info("Cleaning up data/logs");
|
|
Juke.rm("data/logs", { recursive: true });
|
|
},
|
|
});
|
|
|
|
export const TgsTarget = new Juke.Target({
|
|
dependsOn: [TguiTarget],
|
|
executes: async () => {
|
|
Juke.logger.info("Prepending TGS define");
|
|
prependDefines("TGS");
|
|
},
|
|
});
|
|
|
|
Juke.setup({ file: import.meta.url }).then((code) => {
|
|
// We're using the currently available quirk in Juke Build, which
|
|
// prevents it from exiting on Windows, to wait on errors.
|
|
if (code !== 0 && process.argv.includes("--wait-on-error")) {
|
|
Juke.logger.error("Please inspect the error and close the window.");
|
|
return;
|
|
}
|
|
|
|
if (TGS_MODE) {
|
|
// workaround for ESBuild process lingering
|
|
// Once https://github.com/privatenumber/esbuild-loader/pull/354 is merged and updated to, this can be removed
|
|
setTimeout(() => process.exit(code), 10000);
|
|
} else {
|
|
process.exit(code);
|
|
}
|
|
});
|
|
|
|
export default TGS_MODE ? TgsTarget : BuildTarget;
|