mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2025-12-09 14:27:09 +00:00
433 lines
12 KiB
JavaScript
Executable File
433 lines
12 KiB
JavaScript
Executable File
#!/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 fs from 'fs';
|
|
import https from 'https';
|
|
import Juke from './juke/index.js';
|
|
import { DreamDaemon, DreamMaker } from './lib/byond.js';
|
|
import { yarn } from './lib/yarn.js';
|
|
|
|
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;
|
|
}
|
|
process.exit(code);
|
|
});
|
|
|
|
const DME_NAME = 'citadel';
|
|
const CUTTER_SUFFIX = '.png.toml'
|
|
|
|
// 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 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`,
|
|
`icon_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",
|
|
"icon_cutter_templates",
|
|
"icons",
|
|
]);
|
|
},
|
|
});
|
|
|
|
|
|
|
|
|
|
export const DmMapsIncludeTarget = new Juke.Target({
|
|
executes: async () => {
|
|
// include all maps
|
|
// exclude WIP maps
|
|
const folders = [
|
|
...Juke.glob('maps/**/*.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,
|
|
WarningParameter,
|
|
NoWarningParameter,
|
|
SkipIconCutter,
|
|
],
|
|
dependsOn: ({ get }) => [
|
|
get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget,
|
|
!get(SkipIconCutter) && IconCutterTarget,
|
|
],
|
|
inputs: [
|
|
'maps/**/*.dm',
|
|
'code/**',
|
|
'html/**',
|
|
'icons/**',
|
|
'interface/**',
|
|
`${DME_NAME}.dme`,
|
|
],
|
|
outputs: [
|
|
`${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),
|
|
});
|
|
},
|
|
});
|
|
|
|
export const DmTestTarget = new Juke.Target({
|
|
parameters: [
|
|
DefineParameter,
|
|
WarningParameter,
|
|
],
|
|
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'),
|
|
});
|
|
Juke.rm('data/logs/ci', { recursive: true });
|
|
await DreamDaemon(
|
|
`${DME_NAME}.test.dmb`,
|
|
'-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 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|cjs|svg)',
|
|
'tgui/packages/tgfont/package.json',
|
|
],
|
|
outputs: [
|
|
'tgui/packages/tgfont/dist/tgfont.css',
|
|
'tgui/packages/tgfont/dist/tgfont.eot',
|
|
'tgui/packages/tgfont/dist/tgfont.woff2',
|
|
],
|
|
executes: () => yarn('tgfont:build'),
|
|
});
|
|
|
|
export const TguiTarget = new Juke.Target({
|
|
dependsOn: [YarnTarget],
|
|
inputs: [
|
|
'tgui/.yarn/install-target',
|
|
'tgui/webpack.config.js',
|
|
'tgui/**/package.json',
|
|
'tgui/packages/**/*.+(js|cjs|ts|tsx|scss)',
|
|
],
|
|
outputs: [
|
|
'tgui/public/tgui.bundle.css',
|
|
'tgui/public/tgui.bundle.js',
|
|
'tgui/public/tgui-panel.bundle.css',
|
|
'tgui/public/tgui-panel.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 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, 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 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({
|
|
dependsOn: [BuildTarget],
|
|
executes: async ({ get }) => {
|
|
const port = get(PortParameter) || '1337';
|
|
await DreamDaemon(`${DME_NAME}.dmb`, port, '-trusted');
|
|
},
|
|
});
|
|
|
|
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,webpack}', { 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('*.mdme*');
|
|
Juke.rm('*.m.*');
|
|
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');
|
|
},
|
|
});
|
|
|
|
const TGS_MODE = process.env.CBT_BUILD_MODE === 'TGS';
|
|
|
|
export default TGS_MODE ? TgsTarget : BuildTarget;
|