├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── scripts ├── compiler.js ├── copy.js ├── shared.js └── watch.js ├── server.toml ├── src ├── core │ ├── client │ │ └── startup.ts │ ├── resource.toml │ └── server │ │ ├── secondFile.ts │ │ └── startup.ts └── dbg_reconnect │ ├── resource.toml │ └── server │ ├── ipc.ts │ ├── reconnect.ts │ └── startup.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [stuyk] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | altv-server 3 | resources 4 | .vscode 5 | *.dll 6 | *.exe 7 | update.json 8 | modules/ 9 | .server-crashes-cache 10 | data/ 11 | *.log 12 | cache/ 13 | crashdumps/ 14 | package-lock.json 15 | ipc.txt -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 120, 4 | "singleQuote": true, 5 | "semi": true, 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Typescript Boilerplate for alt:V - v5.3.0 3 |

4 |

5 | 6 |

7 | 8 |

9 | Super Fast Compilation 10 |

11 | 12 | > [!IMPORTANT] 13 | > This boilerplate will no longer be updated. 14 | > Consider using [Rebar Framework](https://rebarv.com/) for more up-to-date practices, and a plugin system. 15 | 16 | # Variants 17 | 18 | - [TypeScript Boilerplate with Vue](https://github.com/Stuyk/altv-typescript-vue) 19 | - [TypeScript Boilerplate with React](https://github.com/Stuyk/altv-typescript-react) 20 | 21 | # Features 22 | 23 | A simple Typescript Boilerplate that builds incredibly fast using the [SWC Library](https://github.com/swc-project/swc). 24 | 25 | - Auto Refresh Server 26 | - Auto Compile TypeScript Files 27 | - Auto Download Resources 28 | - Single Resource Code Support 29 | - Fastest Auto Reconnect Time on Recompile 30 | - Compile Multiple Resources at Once 31 | 32 | # Installation 33 | 34 | * [Install NodeJS 18+](https://nodejs.org/en/download/current/) 35 | * [Install GIT](https://git-scm.com/downloads) 36 | 37 | ## Clone the Repository 38 | 39 | Use the command below in any terminal, command prompt, etc. 40 | 41 | ```sh 42 | git clone https://github.com/Stuyk/altv-typescript 43 | ``` 44 | 45 | ## Install the Repository 46 | 47 | Use the command below in any terminal, command prompt, etc. 48 | 49 | ```sh 50 | cd altv-typescript 51 | npm install 52 | ``` 53 | 54 | ## Download Server Files 55 | 56 | Use the command below in any terminal, command prompt, etc. This will download all necessary server files from an additional package used by this project. 57 | 58 | ```sh 59 | npm run update 60 | ``` 61 | 62 | ## Start Production Server (Windows) 63 | 64 | Run this command to run the server in production mode. 65 | 66 | ``` 67 | npm run windows 68 | ``` 69 | 70 | ## Start Production Server (Linux) 71 | 72 | Run this command to run the server in production mode. 73 | 74 | ``` 75 | npm run linux 76 | ``` 77 | 78 | ## Start Developer Server 79 | 80 | Run this command to run the server in development mode. 81 | 82 | This will automatically reconnect your alt:V Client if you have `debug` mode turned on. 83 | 84 | ``` 85 | npm run dev 86 | ``` 87 | 88 | ## End Server Runtime 89 | 90 | Use the key combination `ctrl + c` to kill your server in your terminal, command prompt, etc. 91 | 92 | ## How to Add Mods, and New Resources 93 | 94 | Always add your already compiled resources & mods into the `resources` folder. 95 | 96 | ## How to ignore specific resources from compiling 97 | 98 | Add file name `.nocompile` to the resource folder you want to ignore from compiling. 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "altv-quickstart-ts", 3 | "version": "5.3.0", 4 | "description": "TypeScript with alt:V Made Easy", 5 | "scripts": { 6 | "update": "altv-pkg d release", 7 | "build": "node ./scripts/compiler.js && node ./scripts/copy.js", 8 | "windows": "npm run build && altv-server.exe", 9 | "linux": "npm run build && ./altv-server", 10 | "dev": "node ./scripts/watch.js" 11 | }, 12 | "author": "stuyk", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@altv/types-client": "^15.0.9", 16 | "@altv/types-natives": "^15.0.8", 17 | "@altv/types-server": "^15.0.8", 18 | "@altv/types-shared": "^15.0.8", 19 | "@altv/types-webview": "^15.0.8", 20 | "@swc/cli": "^0.1.62", 21 | "@swc/core": "^1.3.95", 22 | "@types/node": "^20.8.8", 23 | "altv-pkg": "^2.6.0", 24 | "fs-extra": "^11.1.1", 25 | "glob": "^10.3.10", 26 | "typescript": "latest", 27 | "watcher": "^2.3.0" 28 | }, 29 | "prettier": { 30 | "tabWidth": 4, 31 | "semi": true, 32 | "printWidth": 120, 33 | "arrowParens": "always", 34 | "singleQuote": true 35 | }, 36 | "type": "module", 37 | "dependencies": { 38 | "fkill": "^8.1.1", 39 | "toml": "^3.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /scripts/compiler.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import * as glob from 'glob'; 3 | import swc from '@swc/core' 4 | import { getResources, normalizeFilePath } from './shared.js'; 5 | 6 | const SWC_CONFIG = { 7 | jsc: { 8 | parser: { 9 | syntax: 'typescript', 10 | dynamicImport: true, 11 | decorators: true, 12 | }, 13 | transform: { 14 | legacyDecorator: true, 15 | decoratorMetadata: true, 16 | }, 17 | target: 'es2020', 18 | }, 19 | sourceMaps: false, 20 | }; 21 | 22 | async function buildTargetResource(name) { 23 | const startTime = Date.now(); 24 | 25 | if (fs.existsSync(`resources/${name}`)) { 26 | fs.rmSync(`resources/${name}`, { force: true, recursive: true }); 27 | } 28 | 29 | const filesToCompile = glob.sync(`./src/${name}/**/*.ts`); 30 | let compileCount = 0; 31 | for (let i = 0; i < filesToCompile.length; i++) { 32 | const filePath = normalizeFilePath(filesToCompile[i]); 33 | const finalPath = filePath.replace('src/', 'resources/').replace('.ts', '.js'); 34 | const compiled = swc.transformFileSync(filePath, SWC_CONFIG); 35 | fs.outputFileSync(finalPath, compiled.code, { encoding: 'utf-8' }); 36 | compileCount += 1; 37 | } 38 | 39 | console.log(`[${name}] Has built ${compileCount} files in ${Date.now() - startTime}ms`) 40 | } 41 | 42 | for (let resource of getResources()) { 43 | buildTargetResource(resource); 44 | } -------------------------------------------------------------------------------- /scripts/copy.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import * as glob from 'glob'; 3 | import { getResources, normalizeFilePath } from './shared.js'; 4 | 5 | async function copyResourceAssets(name) { 6 | const startTime = Date.now(); 7 | const files = glob.sync(`./src/${name}/**/*.!(ts)`); 8 | 9 | let filesCopied = 0; 10 | for (let file of files) { 11 | const filePath = normalizeFilePath(file); 12 | const finalPath = filePath.replace('src/', 'resources/'); 13 | fs.copySync(filePath, finalPath, { overwrite: true }); 14 | filesCopied += 1; 15 | } 16 | 17 | console.log(`[${name}] | ${filesCopied} Files Moved | ${Date.now() - startTime}ms`); 18 | } 19 | 20 | for (let resource of getResources()) { 21 | copyResourceAssets(resource); 22 | } -------------------------------------------------------------------------------- /scripts/shared.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import toml from 'toml'; 3 | 4 | export function normalizeFilePath(filePath) { 5 | return filePath.replace(/\\/gm, '/'); 6 | } 7 | 8 | export function writeToIpc(command) { 9 | fs.appendFileSync('ipc.txt', `\r\n${command}`); 10 | } 11 | 12 | export async function sleep(ms) { 13 | return new Promise((resolve) => { 14 | setTimeout(resolve, ms); 15 | }) 16 | } 17 | 18 | function shouldCompileResource(name) { 19 | const path = `./src/${name}`; 20 | if (!fs.existsSync(path)) { 21 | return false; 22 | } 23 | return !fs.existsSync(`${path}/.nocompile`); 24 | } 25 | 26 | let serverConfigPath = './server.toml'; 27 | export function getResources() { 28 | if (!fs.existsSync(serverConfigPath)) { 29 | console.log('server.toml does not exist, please create one.'); 30 | return []; 31 | } 32 | const fileContents = fs.readFileSync(serverConfigPath, { encoding: 'utf-8' }); 33 | const serverConfig = toml.parse(fileContents); 34 | serverConfig.resources = serverConfig.resources.filter(shouldCompileResource); 35 | return serverConfig.resources; 36 | } 37 | -------------------------------------------------------------------------------- /scripts/watch.js: -------------------------------------------------------------------------------- 1 | import { spawnSync, spawn, ChildProcess } from 'node:child_process' 2 | import Watcher from 'watcher'; 3 | import { writeToIpc, sleep } from './shared.js'; 4 | import fkill from 'fkill' 5 | 6 | const fileWatcher = new Watcher(['./src'], { recursive: true, renameDetection: true }); 7 | const altvProcessName = process.platform === "win32" ? './altv-server.exe' : './altv-server' 8 | 9 | /** @type {ChildProcess} */ 10 | let childProcess = undefined 11 | 12 | function compiler() { 13 | console.log(`Starting Compile`) 14 | spawnSync('node', ['./scripts/compiler.js'], { stdio: 'inherit' }) 15 | spawnSync('node', ['./scripts/copy.js'], { stdio: 'inherit' }) 16 | console.log(`Compile Complete`) 17 | } 18 | 19 | async function reboot() { 20 | writeToIpc('kick-all'); 21 | await sleep(250); 22 | if (childProcess) { 23 | await fkill(':7788') 24 | 25 | if (!childProcess.killed) { 26 | try { 27 | childProcess.kill(); 28 | } catch (err) { } 29 | } 30 | } 31 | 32 | compiler(); 33 | childProcess = spawn(altvProcessName, { stdio: 'inherit' }) 34 | } 35 | 36 | function start() { 37 | fileWatcher.on('change', reboot); 38 | reboot(); 39 | } 40 | 41 | start(); -------------------------------------------------------------------------------- /server.toml: -------------------------------------------------------------------------------- 1 | announce = false 2 | 3 | description = 'test' 4 | website = 'test.com' 5 | gamemode = 'Freeroam' 6 | 7 | language = 'en' 8 | modules = [ 'js-module' ] 9 | name = 'TestServer' 10 | players = 1024 11 | port = 7788 12 | tags = [ 'customTag1', 'customTag2', 'customTag3', 'customTag4' ] 13 | 14 | resources = [ 15 | 'core', 16 | 'dbg_reconnect' 17 | ] 18 | 19 | # Turn this off in production mode 20 | debug = true 21 | 22 | # Leave host as 0.0.0.0, don't change it. 23 | host = '0.0.0.0' 24 | 25 | [js-module] 26 | global-webcrypto = true 27 | global-fetch = true 28 | 29 | [voice] 30 | bitrate = 64000 -------------------------------------------------------------------------------- /src/core/client/startup.ts: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt-client'; 2 | 3 | alt.onServer('log:Console', handleLogConsole); 4 | 5 | function handleLogConsole(msg: string) { 6 | alt.log(msg); 7 | } 8 | -------------------------------------------------------------------------------- /src/core/resource.toml: -------------------------------------------------------------------------------- 1 | type = 'js' 2 | main = 'server/startup.js' 3 | client-files = [ 'client/*', 'shared/*' ] 4 | client-main = 'client/startup.js' 5 | deps = [] -------------------------------------------------------------------------------- /src/core/server/secondFile.ts: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt-server'; 2 | 3 | alt.log('hello from second file'); 4 | -------------------------------------------------------------------------------- /src/core/server/startup.ts: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt-server'; 2 | import './secondFile.js'; // NOTE THE .js extension is required for file imports 3 | 4 | alt.log(`alt:V Server - Boilerplate Started`); 5 | alt.on('playerConnect', handlePlayerConnect); 6 | 7 | function handlePlayerConnect(player: alt.Player) { 8 | alt.log(`[${player.id}] ${player.name} has connected to the server.`); 9 | 10 | player.model = 'mp_m_freemode_01'; 11 | player.spawn(36.19486618041992, 859.3850708007812, 197.71343994140625, 0); 12 | alt.emitClient(player, 'log:Console', 'alt:V Server - Boilerplate Started'); 13 | } 14 | -------------------------------------------------------------------------------- /src/dbg_reconnect/resource.toml: -------------------------------------------------------------------------------- 1 | type = 'js' 2 | main = 'server/startup.js' 3 | deps = [] -------------------------------------------------------------------------------- /src/dbg_reconnect/server/ipc.ts: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt-server'; 2 | import fs from 'fs'; 3 | 4 | const FILE_NAME = 'ipc.txt'; 5 | 6 | function onChange() { 7 | const content = fs.readFileSync(FILE_NAME, { encoding: 'utf-8' }); 8 | const contents = content.split('\n'); 9 | const lastLine = contents[contents.length - 1].replace(/\n/g, ''); 10 | 11 | switch (lastLine) { 12 | case 'kick-all': 13 | alt.log(`Invoking IPC Event 'kick-all'`); 14 | alt.Player.all.forEach((player) => { 15 | player.kick('Restarting Server'); 16 | }); 17 | break; 18 | } 19 | } 20 | 21 | if (alt.debug) { 22 | if (fs.existsSync(FILE_NAME)) { 23 | fs.rmSync(FILE_NAME); 24 | } 25 | 26 | fs.writeFileSync(FILE_NAME, ''); 27 | fs.watch(FILE_NAME, onChange); 28 | alt.log(`Listening for IPC Events in Debug Mode`); 29 | } 30 | -------------------------------------------------------------------------------- /src/dbg_reconnect/server/reconnect.ts: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt-server'; 2 | 3 | const enum Status { 4 | Loading = 'LOADING', 5 | MainMenu = 'MAIN_MENU', 6 | DownloadingFiles = 'DOWNLOADING_FILES', 7 | Connecting = 'CONNECTING', 8 | InGame = 'IN_GAME', 9 | Disconnecting = 'DISCONNECTING', 10 | Error = 'ERROR', 11 | } 12 | 13 | const RETRY_DELAY = 2500; 14 | const DEBUG_PORT = 9223; 15 | 16 | async function getLocalClientStatus(): Promise { 17 | try { 18 | const response = await fetch(`http://127.0.0.1:${DEBUG_PORT}/status`); 19 | return response.text() as unknown as Status; 20 | } catch (error) { 21 | return null; 22 | } 23 | } 24 | 25 | export async function connectLocalClient(): Promise { 26 | const status = await getLocalClientStatus(); 27 | if (status === null) { 28 | return; 29 | } 30 | 31 | if (status !== Status.MainMenu && status !== Status.InGame) { 32 | setTimeout(() => connectLocalClient(), RETRY_DELAY); 33 | } 34 | 35 | try { 36 | await fetch(`http://127.0.0.1:${DEBUG_PORT}/reconnect`, { 37 | method: 'POST', 38 | body: 'serverPassword', // only needed when a password is set in the server.toml 39 | }); 40 | } catch (error) { 41 | console.log(error); 42 | } 43 | } 44 | 45 | if (alt.debug) { 46 | connectLocalClient(); 47 | } 48 | -------------------------------------------------------------------------------- /src/dbg_reconnect/server/startup.ts: -------------------------------------------------------------------------------- 1 | import './ipc.js'; // Used to reconnect, do not remove. 2 | import './reconnect.js'; 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["./node_modules/@types", "./node_modules/@altv"], 4 | "removeComments": true, 5 | "noUnusedLocals": false, 6 | "noUnusedParameters": false, 7 | "noImplicitReturns": true, 8 | "moduleResolution": "node", 9 | "resolveJsonModule": true, 10 | "allowJs": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "module": "esNext", 14 | "target": "esNext", 15 | "outDir": "./resources", 16 | "rootDir": "./src", 17 | "allowSyntheticDefaultImports": true, 18 | "declaration": false 19 | }, 20 | "include": ["./src/**/*.ts"], 21 | "exclude": ["node_modules", "**/*.spec.ts", "resources", "**/*.json"] 22 | } 23 | --------------------------------------------------------------------------------