2023-03-03 16:23:46 +01:00
|
|
|
/* eslint-disable @typescript-eslint/naming-convention */
|
Updated dependencies + A few other things (!150)
Initially this was going to be an update to dependencies but it seems i got a little carried away!
Anyways this PR removes 2 unused dependencies (`jshint` and `utf-8-validate`), and 2 other, `del` and `fs-extra` that were replaced by the built-in `fs/promises`.
It also renames all `tsconfig` and `Dockerfile` files, in a way that when viewed in a file tree sorted alphabetically they will be next to each other.
It also updates the typescript target to `ES2022`, and changes moduleResolution from `Node` to `Node10` (this isn't an update, they are the same thing but `Node` is now deprecated).
It also adds the `node:` discriminator to every import from built-in modules.
It also has major changes to the build script, `del` and `fs-extra` were only being used in the build script, it's now using `fs/promises` instead, cleaned up the code from some functions, adds better documentation to a few functions, and renames some gulp tasks and npm scripts to better represent what they actually do.
And finally it updates dependencies, except for `atomically` which can't be updated unless the project switches to ESM.
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/150
Co-authored-by: TheSparta <thesparta@noreply.dev.sp-tarkov.com>
Co-committed-by: TheSparta <thesparta@noreply.dev.sp-tarkov.com>
2023-10-14 11:01:41 +02:00
|
|
|
import fs from "node:fs";
|
|
|
|
import path from "node:path";
|
2023-10-10 13:03:20 +02:00
|
|
|
import { inject, injectable } from "tsyringe";
|
2023-10-16 19:25:14 +02:00
|
|
|
import ts from "typescript";
|
2023-10-19 19:21:17 +02:00
|
|
|
|
|
|
|
import type { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
|
|
|
import { HashCacheService } from "@spt-aki/services/HashCacheService";
|
|
|
|
import { VFS } from "@spt-aki/utils/VFS";
|
2023-03-03 16:23:46 +01:00
|
|
|
|
|
|
|
@injectable()
|
|
|
|
export class ModCompilerService
|
|
|
|
{
|
2023-10-10 13:03:20 +02:00
|
|
|
protected serverDependencies: string[];
|
|
|
|
|
2023-03-03 16:23:46 +01:00
|
|
|
constructor(
|
|
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
|
|
@inject("HashCacheService") protected hashCacheService: HashCacheService,
|
2023-11-16 22:42:06 +01:00
|
|
|
@inject("VFS") protected vfs: VFS,
|
2023-03-03 16:23:46 +01:00
|
|
|
)
|
2023-10-10 13:03:20 +02:00
|
|
|
{
|
|
|
|
const packageJsonPath: string = path.join(__dirname, "../../package.json");
|
|
|
|
this.serverDependencies = Object.keys(JSON.parse(this.vfs.readFile(packageJsonPath)).dependencies);
|
|
|
|
}
|
2023-03-03 16:23:46 +01:00
|
|
|
|
2023-10-10 13:03:20 +02:00
|
|
|
/**
|
|
|
|
* Convert a mods TS into JS
|
|
|
|
* @param modName Name of mod
|
|
|
|
* @param modPath Dir path to mod
|
2023-11-16 22:42:06 +01:00
|
|
|
* @param modTypeScriptFiles
|
|
|
|
* @returns
|
2023-10-10 13:03:20 +02:00
|
|
|
*/
|
2023-03-03 16:23:46 +01:00
|
|
|
public async compileMod(modName: string, modPath: string, modTypeScriptFiles: string[]): Promise<void>
|
|
|
|
{
|
|
|
|
// Concatenate TS files into one string
|
|
|
|
let tsFileContents: string;
|
|
|
|
let fileExists = true; // does every js file exist (been compiled before)
|
|
|
|
for (const file of modTypeScriptFiles)
|
|
|
|
{
|
2023-03-22 15:31:05 +01:00
|
|
|
const fileContent = this.vfs.readFile(file);
|
2023-11-16 22:42:06 +01:00
|
|
|
tsFileContents += fileContent;
|
2023-03-03 16:23:46 +01:00
|
|
|
|
|
|
|
// Does equivalent .js file exist
|
|
|
|
if (!this.vfs.exists(file.replace(".ts", ".js")))
|
|
|
|
{
|
|
|
|
fileExists = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const hashMatches = this.hashCacheService.modContentMatchesStoredHash(modName, tsFileContents);
|
|
|
|
|
|
|
|
if (fileExists && hashMatches)
|
|
|
|
{
|
|
|
|
// Everything exists and matches, escape early
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hashMatches)
|
|
|
|
{
|
|
|
|
// Store / update hash in json file
|
|
|
|
this.hashCacheService.storeModContent(modName, tsFileContents);
|
|
|
|
}
|
|
|
|
|
2023-11-16 22:42:06 +01:00
|
|
|
return this.compile(modTypeScriptFiles, {
|
|
|
|
noEmitOnError: true,
|
|
|
|
noImplicitAny: false,
|
|
|
|
target: ts.ScriptTarget.ES2022,
|
|
|
|
module: ts.ModuleKind.CommonJS,
|
|
|
|
moduleResolution: ts.ModuleResolutionKind.Node10,
|
|
|
|
sourceMap: true,
|
|
|
|
resolveJsonModule: true,
|
|
|
|
allowJs: true,
|
|
|
|
esModuleInterop: true,
|
|
|
|
downlevelIteration: true,
|
|
|
|
experimentalDecorators: true,
|
|
|
|
emitDecoratorMetadata: true,
|
|
|
|
rootDir: modPath,
|
|
|
|
});
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
|
2023-10-10 13:03:20 +02:00
|
|
|
/**
|
|
|
|
* Convert a TS file into JS
|
|
|
|
* @param fileNames Paths to TS files
|
|
|
|
* @param options Compiler options
|
|
|
|
*/
|
2023-10-16 19:25:14 +02:00
|
|
|
protected async compile(fileNames: string[], options: ts.CompilerOptions): Promise<void>
|
2023-03-03 16:23:46 +01:00
|
|
|
{
|
2023-10-16 19:25:14 +02:00
|
|
|
// C:/snapshot/project || /snapshot/project
|
2023-11-16 22:42:06 +01:00
|
|
|
const baseDir: string = __dirname.replace(/\\/g, "/").split("/").slice(0, 3).join("/");
|
2023-03-03 16:23:46 +01:00
|
|
|
|
|
|
|
for (const filePath of fileNames)
|
|
|
|
{
|
2023-10-16 19:25:14 +02:00
|
|
|
const destPath = filePath.replace(".ts", ".js");
|
|
|
|
const parsedPath = path.parse(filePath);
|
|
|
|
const parsedDestPath = path.parse(destPath);
|
|
|
|
const text = fs.readFileSync(filePath).toString();
|
2023-10-10 13:03:20 +02:00
|
|
|
let replacedText: string;
|
2023-10-16 19:25:14 +02:00
|
|
|
|
2023-03-03 16:23:46 +01:00
|
|
|
if (globalThis.G_RELEASE_CONFIGURATION)
|
|
|
|
{
|
2023-10-10 13:03:20 +02:00
|
|
|
replacedText = text.replace(/(@spt-aki)/g, `${baseDir}/obj`);
|
2023-11-16 22:42:06 +01:00
|
|
|
for (const dependency of this.serverDependencies)
|
2023-10-10 13:03:20 +02:00
|
|
|
{
|
|
|
|
replacedText = replacedText.replace(`"${dependency}"`, `"${baseDir}/node_modules/${dependency}"`);
|
|
|
|
}
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-11-16 22:42:06 +01:00
|
|
|
replacedText = text.replace(/(@spt-aki)/g, path.join(__dirname, "..").replace(/\\/g, "/"));
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
|
2023-10-16 19:25:14 +02:00
|
|
|
const output = ts.transpileModule(replacedText, { compilerOptions: options });
|
|
|
|
|
|
|
|
if (output.sourceMapText)
|
|
|
|
{
|
2023-11-16 22:42:06 +01:00
|
|
|
output.outputText = output.outputText.replace(
|
|
|
|
"//# sourceMappingURL=module.js.map",
|
|
|
|
`//# sourceMappingURL=${parsedDestPath.base}.map`,
|
|
|
|
);
|
2023-10-16 19:25:14 +02:00
|
|
|
|
|
|
|
const sourceMap = JSON.parse(output.sourceMapText);
|
|
|
|
sourceMap.file = parsedDestPath.base;
|
2023-11-16 22:42:06 +01:00
|
|
|
sourceMap.sources = [parsedPath.base];
|
2023-10-16 19:25:14 +02:00
|
|
|
|
|
|
|
fs.writeFileSync(`${destPath}.map`, JSON.stringify(sourceMap));
|
|
|
|
}
|
|
|
|
fs.writeFileSync(destPath, output.outputText);
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
while (!this.areFilesReady(fileNames))
|
|
|
|
{
|
|
|
|
await this.delay(200);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-10 13:03:20 +02:00
|
|
|
/**
|
|
|
|
* Do the files at the provided paths exist
|
2023-11-16 22:42:06 +01:00
|
|
|
* @param fileNames
|
|
|
|
* @returns
|
2023-10-10 13:03:20 +02:00
|
|
|
*/
|
2023-03-03 16:23:46 +01:00
|
|
|
protected areFilesReady(fileNames: string[]): boolean
|
|
|
|
{
|
2023-11-16 22:42:06 +01:00
|
|
|
return fileNames.filter((x) => !this.vfs.exists(x.replace(".ts", ".js"))).length === 0;
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
|
2023-10-10 13:03:20 +02:00
|
|
|
/**
|
|
|
|
* Wait the provided number of milliseconds
|
|
|
|
* @param ms Milliseconds
|
2023-11-16 22:42:06 +01:00
|
|
|
* @returns
|
2023-10-10 13:03:20 +02:00
|
|
|
*/
|
2023-03-03 16:23:46 +01:00
|
|
|
protected delay(ms: number): Promise<unknown>
|
|
|
|
{
|
2023-11-16 22:42:06 +01:00
|
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
2023-11-16 22:42:06 +01:00
|
|
|
}
|