Skip to content

Commit

Permalink
Merge branch 'tabarra:develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Gravxd authored Feb 17, 2025
2 parents 8b0ca23 + de488ed commit 48b8f16
Show file tree
Hide file tree
Showing 215 changed files with 15,319 additions and 8,351 deletions.
6 changes: 6 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
The `!NC` comments in code are tags used by a git pre-commit hook to prevent committing the lines containing it, and are generally used to mark TO-DOs that are required to be done before committing the changes.
Import `suite, it, expect` from vitest for writing tests, whith each method having one `suite()` and a list of tests using the `it()` for its definition.
Prefer implicit over explicit function return types.
Except for React components, prefer arrow functions.
Prefer using for..of instead of forEach.
Prefer single quotes over doble quotes.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
Join our Discord Server: &nbsp; <a href="https://discord.gg/AFAAXzq"><img src="https://discordapp.com/api/guilds/577993482761928734/widget.png?style=shield"></img></a>
</h4>
<p align="center">
<b>txAdmin</b> is a <b>full featured</b> web panel to Manage & Monitor your FiveM/RedM Server remotely, in use by over <strong>21.000</strong> servers worldwide at any given time.
<b>txAdmin</b> is _the_ <b>full featured</b> web panel to Manage & Monitor your FiveM/RedM Server remotely, in use by over <strong>29.000</strong> servers worldwide at any given time.
</p>
<p align="center">
<a href="https://zap-hosting.com/txadmin4" target="_blank" rel="noopener">
Expand Down
2 changes: 1 addition & 1 deletion core/.npm-upgrade.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@
"reason": "undici sub-dependency dropped support for node 16"
}
}
}
}
7 changes: 2 additions & 5 deletions core/boot/checkPreRelease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@ import humanizeDuration, { Unit } from 'humanize-duration';
import chalk from 'chalk';
import consoleFactory from '@lib/console';
import fatalError from '@lib/fatalError';
import { msToDuration } from '@lib/misc';
const console = consoleFactory('ATTENTION');


//@ts-ignore esbuild will replace TX_PRERELEASE_EXPIRATION with a string
const PRERELEASE_EXPIRATION = parseInt(TX_PRERELEASE_EXPIRATION)
const humanizeOptions = {
round: true,
units: ['d', 'h', 'm'] as Unit[],
};

const expiredError = [
'This pre-release version has expired, please update your txAdmin.',
'Bye bye 👋',
]

const printExpirationBanner = (timeUntilExpiration: number) => {
const timeLeft = humanizeDuration(timeUntilExpiration, humanizeOptions)
const timeLeft = msToDuration(timeUntilExpiration)
const timeLeftStyled = chalk.inverse(` ${timeLeft} `);
console.error('This is a pre-release version of txAdmin!');
console.error('This build is meant to be used by txAdmin beta testers.');
Expand Down
29 changes: 5 additions & 24 deletions core/boot/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,10 @@ import chalk from 'chalk';
import consoleFactory from '@lib/console';
import fatalError from '@lib/fatalError';
import { txEnv } from '@core/globalData';
import ConfigStore from '@modules/ConfigStore';
const console = consoleFactory(modulename);


//FIXME: deprecate this - do @modules/ConfigStore/utils.ts > encodeConfig() or something like that
const CONFIG_STRUCTURE = {
global: {
serverName: null,
language: 'en',
},
logger: {},
monitor: {
restarterSchedule: [],
},
webServer: {},
discordBot: {
enabled: false,
token: null,
announceChannel: null,
},
fxRunner: {
serverDataPath: null,
cfgPath: null,
autostart: true,
},
};

/**
* Ensure the profile subfolders exist
*/
Expand All @@ -47,6 +25,7 @@ export const ensureProfileStructure = () => {
}
}


/**
* Setup the profile folder structure
*/
Expand All @@ -56,9 +35,10 @@ export const setupProfile = () => {
console.log('Creating new profile folder...');
try {
fs.mkdirSync(txEnv.profilePath);
const configStructure = ConfigStore.getEmptyConfigFile();
fs.writeFileSync(
path.join(txEnv.profilePath, 'config.json'),
JSON.stringify(CONFIG_STRUCTURE, null, 2)
JSON.stringify(configStructure, null, 2)
);
ensureProfileStructure();
} catch (error) {
Expand All @@ -75,6 +55,7 @@ export const setupProfile = () => {
try {
const fxsPath = path.join(txEnv.fxServerPath, 'FXServer.exe');
const batLines = [
//TODO: add note to not add any server convars in here
`@echo off`,
`"${fxsPath}" +set serverProfile "${txEnv.profile}"`,
`pause`
Expand Down
3 changes: 2 additions & 1 deletion core/boot/startReadyWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const getOSMessage = async () => {
'We recommend renting a server from ' + chalk.inverse(' https://zap-hosting.com/txAdmin ') + '.',
];

//FIXME: use si.osInfo() instead
const distro = await getOsDistro();
return (distro && distro.includes('Linux') || distro.includes('Server'))
? serverMessage
Expand Down Expand Up @@ -153,7 +154,7 @@ export const startReadyWatcher = async (cb: () => void) => {
'',
'Use the PIN below to register:',
chalk.inverse(` ${adminMasterPin} `),
]
];

//Printing stuff
const boxOptions = {
Expand Down
2 changes: 1 addition & 1 deletion core/deployer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class Deployer {
this.originalRecipe = originalRecipe;
this.deploymentID = deploymentID;
this.progress = 0;
this.serverName = customMetaData.serverName || txConfig.global.serverName || '';
this.serverName = customMetaData.serverName || txConfig.general.serverName || '';
this.logLines = [];

//Load recipe
Expand Down
2 changes: 1 addition & 1 deletion core/deployer/recipeParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import YAML from 'js-yaml';
import { txEnv } from '@core/globalData';
import { default as untypedRecipeEngine } from './recipeEngine.js';
import consoleFactory from '@lib/console.js';
import { RECIPE_DEPLOYER_VERSION } from './index.js';
import { RECIPE_DEPLOYER_VERSION } from './index.js'; //FIXME: circular_dependency
const console = consoleFactory(modulename);


Expand Down
47 changes: 13 additions & 34 deletions core/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,30 @@
//NOTE: don't import anything at the root of this file or it breaks the type definitions

/**
* MARK: txAdmin stuff
*/
type RefreshConfigFunc = import('@modules/ConfigStore/').RefreshConfigFunc;
interface GenericTxModuleInstance {
public handleConfigUpdate?: RefreshConfigFunc;
// public measureMemory?: () => { [key: string]: number };
}
declare interface GenericTxModule<T> {
new(): T;
// configKeys: string[]; //TODO
new(): InstanceType<T> & GenericTxModuleInstance;
static readonly configKeysWatched?: string[];
}

declare type TxConfigs = import('@modules/ConfigStore/schema').TxConfigs
declare const txConfig: TxConfigs;

declare type TxCoreType = import('./txAdmin').TxCoreType;
declare const txCore: TxCoreType;

declare type TxManagerType = import('./txManager').TxManagerType;
declare const txManager: TxManagerType;

//FIXME: temporary
type AnythingButConfig = {
config: never;
[key: string]: any;
};
declare type TxAdminConfigs = {
config: never,
global: {
serverName: string,
language: string,
menuEnabled: boolean,
menuAlignRight: boolean,
menuPageKey: string,

hideDefaultAnnouncement: boolean,
hideDefaultDirectMessage: boolean,
hideDefaultWarning: boolean,
hideDefaultScheduledRestartWarning: boolean,
hideAdminInPunishments: boolean,
hideAdminInMessages: boolean,
},
logger: AnythingButConfig,
monitor: AnythingButConfig,
playerDatabase: import('@modules/Database').PlayerDbConfigType,
webServer: import('@modules/WebServer').WebServerConfigType,
discordBot: import('@modules/DiscordBot').DiscordBotConfigType,
fxRunner: AnythingButConfig,
banTemplates: AnythingButConfig,
};
declare let txConfig: TxAdminConfigs;

declare type TxConsole = import('./lib/console').TxConsole;
declare namespace globalThis {
interface Console extends TxConsole {}
interface Console extends TxConsole { }
}


Expand Down
48 changes: 48 additions & 0 deletions core/lib/console.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { suite, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { processStdioWriteRaw, processStdioEnsureEol } from './console';


suite('processStdioWriteRaw & processStdioEnsureEol', () => {
let writeSpy: ReturnType<typeof vi.spyOn>;

beforeEach(() => {
writeSpy = vi.spyOn(process.stdout as any, 'write').mockImplementation(() => true);
});

afterEach(() => {
writeSpy.mockRestore();
});

it('should write a non-newline string and then add a newline', () => {
processStdioWriteRaw("Hello");
expect(writeSpy).toHaveBeenCalledWith("Hello");
processStdioEnsureEol();
expect(writeSpy).toHaveBeenCalledWith('\n');
});

it('should write a string ending in newline without adding an extra one', () => {
processStdioWriteRaw("Hello\n");
expect(writeSpy).toHaveBeenCalledWith("Hello\n");
writeSpy.mockClear();
processStdioEnsureEol();
expect(writeSpy).not.toHaveBeenCalled();
});

it('should write Uint8Array without trailing newline and then add one', () => {
const buffer = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
processStdioWriteRaw(buffer);
expect(writeSpy).toHaveBeenCalledWith(buffer);
processStdioEnsureEol();
expect(writeSpy).toHaveBeenCalledWith('\n');
});

it('should write Uint8Array with trailing newline and not add an extra one', () => {
const newline = 10;
const buffer = new Uint8Array([72, 101, 108, 108, 111, newline]); // "Hello\n"
processStdioWriteRaw(buffer);
expect(writeSpy).toHaveBeenCalledWith(buffer);
writeSpy.mockClear();
processStdioEnsureEol();
expect(writeSpy).not.toHaveBeenCalled();
});
});
78 changes: 56 additions & 22 deletions core/lib/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ export const setConsoleEnvData = (
}


/**
* STDOUT EOL helper
*/
let stdioEolPending = false;
export const processStdioWriteRaw = (buffer: Uint8Array | string) => {
if (!buffer.length) return;
const comparator = typeof buffer === 'string' ? '\n' : 10;
stdioEolPending = buffer[buffer.length - 1] !== comparator;
process.stdout.write(buffer);
}
export const processStdioEnsureEol = () => {
if (stdioEolPending) {
process.stdout.write('\n');
stdioEolPending = false;
}
}


/**
* New console and streams
*/
Expand Down Expand Up @@ -247,24 +265,37 @@ const getLogFunc = (
}
}

type LogFunction = (message?: any, ...optParams: any) => void
//Reused types
type LogFunction = typeof Console.prototype.log;
type DirFunction = (data: any, options?: TxInspectOptions) => void;
interface TxBaseLogTypes {
debug: LogFunction;
log: LogFunction;
ok: LogFunction;
warn: LogFunction;
error: LogFunction;
dir: DirFunction;
}


/**
* Factory for console.log drop-ins
*/
const consoleFactory = (ctx?: string, subCtx?: string): CombinedConsole => {
const currContext = [header, ctx, subCtx].filter(x => x).join(':');

return {
...defaultConsole,
tag: (subCtx: string) => consoleFactory(ctx, subCtx),
const baseLogs: TxBaseLogTypes = {
debug: getLogFunc(currContext, DEBUG_COLOR),
log: getLogFunc(currContext, chalk.bgBlue),
ok: getLogFunc(currContext, chalk.bgGreen),
warn: getLogFunc(currContext, chalk.bgYellow),
error: getLogFunc(currContext, chalk.bgRed),
dir: (data: any, options?: TxInspectOptions & {}) => dirHandler.call(null, data, options),
};

return {
...defaultConsole,
...baseLogs,
tag: (subCtx: string) => consoleFactory(ctx, subCtx),
multiline: (text: string | string[], color: ChalkInstance) => {
if (!Array.isArray(text)) text = text.split('\n');
const prefix = genLogPrefix(currContext, color);
Expand All @@ -291,20 +322,35 @@ const consoleFactory = (ctx?: string, subCtx?: string): CombinedConsole => {
defaultConsole.log(prefix, DIVIDER);
},

//Returns a set of log functions that will be executed after a delay
defer: (ms = 250) => ({
debug: (...args) => setTimeout(() => baseLogs.debug(...args), ms),
log: (...args) => setTimeout(() => baseLogs.log(...args), ms),
ok: (...args) => setTimeout(() => baseLogs.ok(...args), ms),
warn: (...args) => setTimeout(() => baseLogs.warn(...args), ms),
error: (...args) => setTimeout(() => baseLogs.error(...args), ms),
dir: (...args) => setTimeout(() => baseLogs.dir(...args), ms),
}),

//Log functions that will output tothe verbose stream
verbose: {
debug: getLogFunc(currContext, DEBUG_COLOR, verboseConsole),
log: getLogFunc(currContext, chalk.bgBlue, verboseConsole),
ok: getLogFunc(currContext, chalk.bgGreen, verboseConsole),
warn: getLogFunc(currContext, chalk.bgYellow, verboseConsole),
error: getLogFunc(currContext, chalk.bgRed, verboseConsole),
dir: (data: any, options?: TxInspectOptions) => dirHandler.call(null, data, options, verboseConsole)
dir: (...args) => dirHandler.call(null, ...args, verboseConsole)
},

//Verbosity getter and explicit setter
get isVerbose() {
return _verboseFlag
},
setVerbose: (state: boolean) => {
_verboseFlag = !!state;
},

//Consts used by the fatalError util
DIVIDER,
DIVIDER_CHAR,
DIVIDER_SIZE,
Expand All @@ -313,27 +359,15 @@ const consoleFactory = (ctx?: string, subCtx?: string): CombinedConsole => {
export default consoleFactory;

interface CombinedConsole extends TxConsole, Console {
dir: LogFunction;
dir: DirFunction;
}

export interface TxConsole {
export interface TxConsole extends TxBaseLogTypes {
tag: (subCtx: string) => TxConsole;
debug: LogFunction;
log: LogFunction;
ok: LogFunction;
warn: LogFunction;
error: LogFunction;
dir: (data: any, options?: TxInspectOptions) => void;
multiline: (text: string | string[], color: ChalkInstance) => void;
majorMultilineError: (text: string | (string | null)[]) => void;
verbose: {
debug: LogFunction;
log: LogFunction;
ok: LogFunction;
warn: LogFunction;
error: LogFunction;
dir: (data: any, options?: TxInspectOptions) => void;
};
defer: (ms?: number) => TxBaseLogTypes;
verbose: TxBaseLogTypes;
readonly isVerbose: boolean;
setVerbose: (state: boolean) => void;
DIVIDER: string;
Expand Down
Loading

0 comments on commit 48b8f16

Please sign in to comment.