mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-13 11:43:31 +00:00
Co-authored-by: Cameron Lennox <killer65311@gmail.com> Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
550 lines
16 KiB
JavaScript
550 lines
16 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Build script for CHOMPStation codebase.
|
|
*
|
|
* This script uses Juke Build, read the docs here:
|
|
* https://github.com/stylemistake/juke-build
|
|
*/
|
|
|
|
import fs from "fs";
|
|
import https from "https";
|
|
import { env } from "process";
|
|
import Juke from "./juke/index.js";
|
|
import { DreamDaemon, DreamMaker, NamedVersionFile } from "./lib/byond.js";
|
|
import { yarn } from "./lib/yarn.js";
|
|
|
|
const TGS_MODE = process.env.CBT_BUILD_MODE === "TGS";
|
|
|
|
Juke.chdir("../..", import.meta.url);
|
|
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);
|
|
}
|
|
});
|
|
|
|
const DME_NAME = "vorestation";
|
|
|
|
// Stores the contents of dependencies.sh as a key value pair
|
|
// Best way I could figure to get ahold of this stuff
|
|
const dependencies = fs
|
|
.readFileSync("dependencies.sh", "utf8")
|
|
.split("\n")
|
|
.map((statement) => statement.replace("export", "").trim())
|
|
.filter((value) => !(value == "" || value.startsWith("#")))
|
|
.map((statement) => statement.split("="))
|
|
.reduce((acc, kv_pair) => {
|
|
acc[kv_pair[0]] = kv_pair[1];
|
|
return acc;
|
|
}, {});
|
|
|
|
// Canonical path for the cutter exe at this moment
|
|
const 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 download_file(download_from, cutter_path);
|
|
if (process.platform !== "win32") {
|
|
await Juke.exec("chmod", ["+x", cutter_path]);
|
|
}
|
|
},
|
|
});
|
|
|
|
async function download_file(url, file) {
|
|
return new Promise((resolve, reject) => {
|
|
let file_stream = fs.createWriteStream(file);
|
|
https
|
|
.get(url, function (response) {
|
|
if (response.statusCode === 302) {
|
|
file_stream.close();
|
|
download_file(response.headers.location, file).then((value) =>
|
|
resolve(),
|
|
);
|
|
return;
|
|
}
|
|
if (response.statusCode !== 200) {
|
|
Juke.logger.error(
|
|
`Failed to download ${url}: Status ${response.statusCode}`,
|
|
);
|
|
file_stream.close();
|
|
reject();
|
|
return;
|
|
}
|
|
response.pipe(file_stream);
|
|
|
|
// after download completed close filestream
|
|
file_stream.on("finish", () => {
|
|
file_stream.close();
|
|
resolve();
|
|
});
|
|
})
|
|
.on("error", (err) => {
|
|
file_stream.close();
|
|
Juke.rm(download_into);
|
|
Juke.logger.error(`Failed to download ${url}: ${err.message}`);
|
|
reject();
|
|
});
|
|
});
|
|
}
|
|
|
|
export const IconCutterTarget = new Juke.Target({
|
|
parameters: [ForceRecutParameter],
|
|
dependsOn: () => [CutterTarget],
|
|
inputs: ({ get }) => {
|
|
const standard_inputs = [
|
|
`icons/**/*.png.toml`,
|
|
`icons/**/*.dmi.toml`,
|
|
`cutter_templates/**/*.toml`,
|
|
"tgui/public/tgui.html",
|
|
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`, `modular_chomp/icons/**/*.png.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"),
|
|
...Juke.glob("modular_chomp/maps/soluna_nexus/**/*.dmm"),
|
|
...Juke.glob("modular_chomp/maps/southern_cross/**/*.dmm"),
|
|
...Juke.glob("modular_chomp/maps/relic_base/**/*.dmm"),
|
|
...Juke.glob("modular_chomp/maps/submap/**/*.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",
|
|
"modular_chomp/code/**",
|
|
"modular_chomp/icons/**",
|
|
"modular_chomp/maps/**/*.dm",
|
|
"modular_chomp/maps/soluna_nexus/**/*.dmm", // Placed here so it recompiles on map changes
|
|
"modular_chomp/maps/southern_cross/**/*.dmm", // Placed here so it recompiles on map changes
|
|
"modular_chomp/maps/relic_base/**/*.dmm", // Placed here so it recompiles on map changes
|
|
"modular_chomp/maps/submap/**/*.dmm", // Placed here so it recompiles on map changes
|
|
`${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);
|
|
}
|
|
},
|
|
});
|
|
|
|
/* We don't have Autowiki
|
|
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 YarnTarget = new Juke.Target({
|
|
parameters: [CiParameter],
|
|
inputs: [
|
|
"tgui/.yarn/+(cache|releases|plugins|sdks)/**/*",
|
|
"tgui/**/package.json",
|
|
"tgui/yarn.lock",
|
|
],
|
|
outputs: ["tgui/.yarn/install-target"],
|
|
executes: ({ get }) => yarn("install", get(CiParameter) && "--immutable"),
|
|
});
|
|
|
|
export const TgFontTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
inputs: [
|
|
"tgui/.yarn/install-target",
|
|
"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 yarn("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: [YarnTarget],
|
|
inputs: [
|
|
"tgui/.yarn/install-target",
|
|
"tgui/rspack.config.cjs",
|
|
"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: () => yarn("tgui:build"),
|
|
});
|
|
|
|
export const TguiEslintTarget = new Juke.Target({
|
|
parameters: [CiParameter],
|
|
dependsOn: [YarnTarget],
|
|
executes: ({ get }) => yarn("tgui:lint", !get(CiParameter) && "--fix"),
|
|
});
|
|
|
|
export const TguiPrettierTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
executes: () => yarn("tgui:prettier"),
|
|
});
|
|
|
|
export const TguiSonarTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
executes: () => yarn("tgui:sonar"),
|
|
});
|
|
|
|
export const TguiTscTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
executes: () => yarn("tgui:tsc"),
|
|
});
|
|
|
|
export const TguiTestTarget = new Juke.Target({
|
|
parameters: [CiParameter],
|
|
dependsOn: [YarnTarget],
|
|
executes: ({ get }) =>
|
|
yarn(`tgui:test-${get(CiParameter) ? "ci" : "simple"}`),
|
|
});
|
|
|
|
export const TguiLintTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget, TguiPrettierTarget, TguiEslintTarget, TguiTscTarget],
|
|
});
|
|
|
|
export const TguiDevTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
executes: ({ args }) => yarn("tgui:dev", ...args),
|
|
});
|
|
|
|
export const TguiAnalyzeTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
executes: () => yarn("tgui:analyze"),
|
|
});
|
|
|
|
export const TguiBenchTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
executes: () => yarn("tgui:bench"),
|
|
});
|
|
|
|
export const TguiPrettierFix = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
executes: () => yarn("tgui:prettier-fix"),
|
|
});
|
|
|
|
export const TguiEslintFix = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
executes: () => yarn("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/.yarn/{cache,unplugged,rspack}", { recursive: true });
|
|
Juke.rm("tgui/.yarn/build-state.yml");
|
|
Juke.rm("tgui/.yarn/install-state.gz");
|
|
Juke.rm("tgui/.yarn/install-target");
|
|
Juke.rm("tgui/.pnp.*");
|
|
},
|
|
});
|
|
|
|
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 });
|
|
Juke.logger.info("Cleaning up global yarn cache");
|
|
await yarn("cache", "clean", "--all");
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Prepends the defines to the .dme.
|
|
* Does not clean them up, as this is intended for TGS which
|
|
* clones new copies anyway.
|
|
*/
|
|
const prependDefines = (...defines) => {
|
|
const dmeContents = fs.readFileSync(`${DME_NAME}.dme`);
|
|
const textToWrite = defines.map((define) => `#define ${define}\n`);
|
|
fs.writeFileSync(`${DME_NAME}.dme`, `${textToWrite}\n${dmeContents}`);
|
|
};
|
|
|
|
export const TgsTarget = new Juke.Target({
|
|
dependsOn: [TguiTarget],
|
|
executes: async () => {
|
|
Juke.logger.info("Prepending TGS define");
|
|
prependDefines("TGS");
|
|
},
|
|
});
|
|
|
|
export default TGS_MODE ? TgsTarget : BuildTarget;
|