Server/project/gulpfile.mjs
chomp 0b67a9a0e9 0.13.5.0 (!147)
Co-authored-by: Dev <dev@dev.sp-tarkov.com>
Co-authored-by: Terkoiz <terkoiz@noreply.dev.sp-tarkov.com>
Co-authored-by: TheSparta <thesparta@noreply.dev.sp-tarkov.com>
Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com>
Co-authored-by: Kaeno <e>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/147
2023-10-10 11:03:20 +00:00

318 lines
9.2 KiB
JavaScript

/* eslint-disable @typescript-eslint/naming-convention */
import crypto from "crypto";
import { deleteSync } from "del";
import fs from "fs-extra";
import gulp from "gulp";
import { exec } from "gulp-execa";
import rename from "gulp-rename";
import os from "os";
import path from "path";
import pkg from "pkg";
import pkgfetch from "pkg-fetch";
import * as ResEdit from "resedit";
import manifest from "./package.json" assert { type: "json" };
const nodeVersion = "node18"; // As of pkg-fetch v3.5, it's v18.15.0
const stdio = "inherit";
const buildDir = "build/";
const dataDir = path.join(buildDir, "Aki_Data", "Server");
const serverExeName = "Aki.Server.exe";
const serverExe = path.join(buildDir, serverExeName);
const pkgConfig = "pkgconfig.json";
const entries = {
release: path.join("obj", "ide", "ReleaseEntry.js"),
debug: path.join("obj", "ide", "DebugEntry.js"),
bleeding: path.join("obj", "ide", "BleedingEdgeEntry.js")
};
const licenseFile = "../LICENSE.md";
// Compilation
const compileTest = async () => exec("swc src -d obj", { stdio });
// Packaging
const fetchPackageImage = async () =>
{
try
{
const output = "./.pkg-cache/v3.5";
const fetchedPkg = await pkgfetch.need({ arch: process.arch, nodeRange: nodeVersion, platform: process.platform, output });
console.log(`fetched node binary at ${fetchedPkg}`);
const builtPkg = fetchedPkg.replace("node", "built");
await fs.copyFile(fetchedPkg, builtPkg);
if (process.platform === "win32" || process.platform === "win64")
{
await exec(`dir ${output}`, {
stdio
});
}
else
{
await exec(`ls ${output}`, {
stdio
});
}
}
catch (e)
{
console.error(`Error while fetching and patching package image: ${e.message}`);
console.error(e.stack);
}
};
const updateBuildProperties = async (cb) =>
{
if(os.platform() !== "win32") {
cb();
return;
}
const exe = ResEdit.NtExecutable.from(fs.readFileSync(serverExe));
const res = ResEdit.NtExecutableResource.from(exe);
const iconPath = path.resolve(manifest.icon);
const iconFile = ResEdit.Data.IconFile.from(fs.readFileSync(iconPath));
ResEdit.Resource.IconGroupEntry.replaceIconsForResource(
res.entries,
1,
1033,
iconFile.icons.map(item => item.data)
);
const vi = ResEdit.Resource.VersionInfo.fromEntries(res.entries)[0];
vi.setStringValues(
{lang: 1033, codepage: 1200},
{
ProductName: manifest.author,
FileDescription: manifest.description,
CompanyName: manifest.name,
LegalCopyright: manifest.license
}
);
vi.removeStringValue({lang: 1033, codepage: 1200}, "OriginalFilename");
vi.removeStringValue({lang: 1033, codepage: 1200}, "InternalName");
vi.setFileVersion(...manifest.version.split(".").map(Number));
vi.setProductVersion(...manifest.version.split(".").map(Number));
vi.outputToResourceEntries(res.entries);
res.outputResource(exe, true);
fs.writeFileSync(serverExe, Buffer.from(exe.generate()));
cb();
};
// Copy various asset files to the destination directory
function copyAssets()
{
return gulp.src(["assets/**/*.json", "assets/**/*.json5", "assets/**/*.png", "assets/**/*.jpg", "assets/**/*.ico"])
.pipe(gulp.dest(dataDir));
}
// Copy executables from node_modules
function copyExecutables()
{
return gulp.src(["node_modules/@pnpm/exe/**/*"])
.pipe(gulp.dest(path.join(dataDir, "@pnpm", "exe")));
}
// Rename and copy the license file
function copyLicense()
{
return gulp.src([licenseFile])
.pipe(rename("LICENSE-Server.txt"))
.pipe(gulp.dest(buildDir));
}
/**
* Writes the latest Git commit hash to the core.json configuration file.
* @param {*} cb Callback to run after completion of function
*/
async function writeCommitHashToCoreJSON(cb)
{
const coreJSONPath = path.resolve(dataDir, "configs", "core.json");
if (fs.existsSync(coreJSONPath))
{
try
{
const coreJSON = fs.readFileSync(coreJSONPath, "utf8");
const parsed = JSON.parse(coreJSON);
// Fetch the latest Git commit hash
const gitResult = await exec("git rev-parse HEAD", { stdout: "pipe" });
// Update the commit hash in the core.json object
parsed.commit = gitResult.stdout.trim() || "";
// Add build timestamp
parsed.buildTime = new Date().getTime();
// Write the updated object back to core.json
fs.writeFileSync(coreJSONPath, JSON.stringify(parsed, null, 4));
}
catch (error)
{
throw new Error(`Failed to write commit hash to core.json: ${error.message}`);
}
}
else
{
console.warn(`core.json not found at ${coreJSONPath}. Skipping commit hash update.`);
}
cb();
}
// Create a hash file for asset checks
async function createHashFile()
{
const hashFileDir = path.resolve(dataDir, "checks.dat");
await fs.createFile(hashFileDir);
const assetData = await loadRecursiveAsync("assets/");
const assetDataString = Buffer.from(JSON.stringify(assetData), "utf-8").toString("base64");
await fs.writeFile(hashFileDir, assetDataString);
}
// Combine all tasks into addAssets
const addAssets = gulp.series(copyAssets, copyExecutables, copyLicense, writeCommitHashToCoreJSON, createHashFile);
// Cleanup
const clean = (cb) =>
{
deleteSync(buildDir, { force: true });
cb();
};
const removeCompiled = async () => fs.rmSync("./obj", { recursive: true, force: true });
// JSON Validation
function getJSONFiles(dir, files = [])
{
const fileList = fs.readdirSync(dir);
for (const file of fileList)
{
const name = path.resolve(dir,file);
if (fs.statSync(name).isDirectory())
{
getJSONFiles(name, files);
}
else if (name.slice(-5) === ".json")
{
files.push(name);
}
}
return files;
}
const validateJSONs = (cb) =>
{
const assetsPath = path.resolve("assets");
const jsonFileList = getJSONFiles(assetsPath);
let jsonFileInProcess = "";
try
{
jsonFileList.forEach((jsonFile) =>
{
jsonFileInProcess = jsonFile;
const jsonString = fs.readFileSync(jsonFile).toString();
JSON.parse(jsonString);
});
cb();
}
catch (error)
{
throw new Error(`${error.message} | ${jsonFileInProcess}`);
}
};
// Hash helper function
const generateHashForData = (data) =>
{
const hashSum = crypto.createHash("sha1");
hashSum.update(data);
return hashSum.digest("hex");
};
// Loader to recursively find all json files in a folder
const loadRecursiveAsync = async (filepath) =>
{
const result = {};
// get all filepaths
const files = fs.readdirSync(filepath).filter((item) =>
{
return fs.statSync(path.join(filepath, item)).isFile();
});
const directories = fs.readdirSync(filepath).filter((item) =>
{
return fs.statSync(path.join(filepath, item)).isDirectory();
});
// add file content to result
for (const file of files)
{
if (file.split(".").pop() === "json")
{
const filename = file.split(".").slice(0, -1).join(".");
const filePathAndName = `${filepath}${file}`;
result[filename] = generateHashForData(fs.readFileSync(filePathAndName));
}
}
// deep tree search
for (const dir of directories)
{
result[dir] = loadRecursiveAsync(`${filepath}${dir}/`);
}
// set all loadRecursive to be executed asynchronously
const resEntries = Object.entries(result);
const resResolved = await Promise.all(resEntries.map(ent => ent[1]));
for (let resIdx = 0; resIdx < resResolved.length; resIdx++)
{
resEntries[resIdx][1] = resResolved[resIdx];
}
// return the result of all async fetch
return Object.fromEntries(resEntries);
};
// Testing
gulp.task("test:debug", async () => exec("ts-node-dev -r tsconfig-paths/register src/ide/TestEntry.ts", { stdio }));
// Main Tasks Generation
const build = (packagingType) =>
{
const anonPackaging = () => packaging(entries[packagingType]);
anonPackaging.displayName = `packaging-${packagingType}`;
const tasks = [clean, validateJSONs, compileTest, fetchPackageImage, anonPackaging, addAssets, updateBuildProperties, removeCompiled];
return gulp.series(tasks);
};
// Packaging Arguments
const packaging = async (entry) =>
{
const target = `${nodeVersion}-${process.platform}-${process.arch}`;
const args = [entry, "--compress", "GZip", "--target", target, "--output", serverExe, "--config", pkgConfig];
try
{
await pkg.exec(args);
}
catch (error)
{
console.error(`Error occurred during packaging: ${error}`);
}
};
// Run server
const runSrv = async (cb) =>
{
await exec("Aki.Server.exe", { stdio, cwd: buildDir });
cb();
};
gulp.task("build:debug", build("debug"));
gulp.task("build:release", build("release"));
gulp.task("build:bleeding", build("bleeding"));
gulp.task("run:server", runSrv);