feat(template): initialize libraries and release v1.0.0
This commit is contained in:
parent
fcdd2d7a50
commit
76d200f068
|
|
@ -2,7 +2,9 @@
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"flameworkVersion": "1.3.2",
|
"flameworkVersion": "1.3.2",
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"crownstudio-roblox-ts-template:out/server/services/startup-service@StartupService": "server/services/startup-service@StartupService"
|
"crownstudio-roblox-ts-template:out/server/services/startup-service@StartupService": "server/services/startup-service@StartupService",
|
||||||
|
"crownstudio-roblox-ts-template:out/client/controllers/cmdr-controller@CmdrController": "client/controllers/cmdr-controller@CmdrController",
|
||||||
|
"crownstudio-roblox-ts-template:out/server/services/cmdr-service@CmdrService": "server/services/cmdr-service@CmdrService"
|
||||||
},
|
},
|
||||||
"metadata": {}
|
"metadata": {}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "crownstudio-roblox-ts-template",
|
"name": "crownstudio-roblox-ts-template",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@11.21.0",
|
"packageManager": "pnpm@11.21.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"check": "pnpm lint && pnpm compile",
|
"test:cmdr": "node --test scripts/cmdr-permissions.test.mjs",
|
||||||
|
"check": "pnpm lint && pnpm test:cmdr && pnpm compile",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"rbxts-build": {
|
"rbxts-build": {
|
||||||
|
|
|
||||||
29
readme.md
29
readme.md
|
|
@ -4,24 +4,19 @@ Template Roblox TypeScript avec Flamework, Rojo et une sélection de bibliothèq
|
||||||
|
|
||||||
## Démarrage
|
## Démarrage
|
||||||
|
|
||||||
Prérequis : [Roblox Studio](https://create.roblox.com/docs/studio/setup), [mise](https://mise.jdx.dev/getting-started.html) et [Rokit](https://github.com/rojo-rbx/rokit#readme). Sous Windows, rouvrir le terminal après leur installation pour actualiser `PATH`.
|
Sous Windows 10/11, lancer depuis le dépôt :
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
mise install
|
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\setup.ps1
|
||||||
rokit install
|
|
||||||
pnpm install --frozen-lockfile
|
|
||||||
pnpm check
|
|
||||||
pnpm build
|
|
||||||
pnpm serve
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`mise` épingle Node.js 24.16.0, pnpm 11.21.0 et uv 0.13.0. `rokit` installe Rojo 7.7.1, Asphalt 2.0.2, darklua 0.19.0 et Mantle 0.11.18. `pnpm build` compile le TypeScript et crée `game.rbxl` avec Rojo. Ouvrir ce fichier dans Studio, ou utiliser `pnpm start` pour le flux [`rbxts-build`](https://github.com/osyrisrblx/rbxts-build#readme). `pnpm watch` lance la compilation et le serveur Rojo en continu. Le projet Rojo est [`default.project.json`](default.project.json) et les entrées client/serveur sont sous [`src`](src/).
|
Le [script d'installation](scripts/setup.ps1) nécessite WinGet (App Installer du Microsoft Store). Il installe Git, [mise](https://mise.jdx.dev/installing-mise.html), Rokit, Roblox Studio et Blender via WinGet, puis les versions définies dans `mise.toml` et `rokit.toml`, et les dépendances pnpm verrouillées. Il termine par `pnpm check` et `pnpm build` et s'arrête dès qu'une commande échoue. Windows peut demander une élévation pour certains installateurs. Une relance conserve les applications déjà installées ; elle ne les met pas à jour automatiquement. Les applications WinGet utilisent la version disponible dans son catalogue, les outils du projet restent épinglés.
|
||||||
|
|
||||||
Autres commandes : `pnpm lint`, `pnpm format:check` et `pnpm format`. [Husky](https://typicode.github.io/husky/) installe un hook local `pre-commit` qui exécute `pnpm lint`. [Renovate](https://docs.renovatebot.com/) lit [`renovate.json`](renovate.json) une fois son application activée sur le dépôt distant.
|
|
||||||
|
|
||||||
## Bibliothèques installées
|
## Bibliothèques installées
|
||||||
|
|
||||||
Chaque paquet de cette section est une dépendance directe. Les liens mènent à la documentation du projet ou à son README actuel ; les versions exécutables sont épinglées dans le lockfile. Les paquets ne sont pas automatiquement utilisés par le code de démarrage.
|
Chaque paquet de cette section est une dépendance directe. Les liens mènent à la documentation du projet ou à son README actuel ; les versions exécutables sont épinglées dans le lockfile. Les 22 bibliothèques ont été examinées : Flamework, son gestionnaire de composants, Cmdr et le logger sont branchés au démarrage ; les autres bibliothèques s'utilisent à l'import ou se configurent avec les données de leur fonctionnalité.
|
||||||
|
|
||||||
|
**Console Cmdr** : en mode Play dans Studio, ouvrir avec **F2**. Les commandes intégrées sont enregistrées et protégées côté serveur. Hors Studio, seuls le propriétaire individuel de l'expérience et les UserIds renseignés dans [`cmdr-permissions.ts`](src/server/config/cmdr-permissions.ts) sont autorisés. Pour une expérience de groupe, renseigner les administrateurs explicitement. `pnpm test:cmdr` vérifie cette politique ; ce test fait aussi partie de `pnpm check`. La configuration client est dans [`cmdr-controller.ts`](src/client/controllers/cmdr-controller.ts).
|
||||||
|
|
||||||
- `@flamework/core` ([documentation](https://flamework.fireboltofdeath.dev/docs/introduction/)) — services, contrôleurs et injection ; le service de démarrage en donne un exemple.
|
- `@flamework/core` ([documentation](https://flamework.fireboltofdeath.dev/docs/introduction/)) — services, contrôleurs et injection ; le service de démarrage en donne un exemple.
|
||||||
- `@flamework/components` ([documentation](https://flamework.fireboltofdeath.dev/docs/additional-modules/components/creating-a-component/)) — composants d'instances.
|
- `@flamework/components` ([documentation](https://flamework.fireboltofdeath.dev/docs/additional-modules/components/creating-a-component/)) — composants d'instances.
|
||||||
|
|
@ -34,17 +29,17 @@ Chaque paquet de cette section est une dépendance directe. Les liens mènent à
|
||||||
- `@rbxts/lapis` ([documentation](https://github.com/nezuo/lapis/tree/master/docs)) — persistance de données.
|
- `@rbxts/lapis` ([documentation](https://github.com/nezuo/lapis/tree/master/docs)) — persistance de données.
|
||||||
- `@rbxts/trove` ([README et API](https://github.com/Sleitnick/rbxts-trove#readme)) — nettoyage des ressources.
|
- `@rbxts/trove` ([README et API](https://github.com/Sleitnick/rbxts-trove#readme)) — nettoyage des ressources.
|
||||||
- `@rbxts/log` ([README](https://github.com/roblox-aurora/rbx-log#readme)) — journalisation.
|
- `@rbxts/log` ([README](https://github.com/roblox-aurora/rbx-log#readme)) — journalisation.
|
||||||
- `@rbxts/sift` ([documentation](https://cxmeel.github.io/sift/)) — collections immuables
|
- `@rbxts/sift` ([documentation](https://cxmeel.github.io/sift/)) — collections immuables
|
||||||
- `@rbxts/t` ([README et API](https://github.com/osyrisrblx/t#readme)) — validation de valeurs à l'exécution.
|
- `@rbxts/t` ([README et API](https://github.com/osyrisrblx/t#readme)) — validation de valeurs à l'exécution.
|
||||||
- `@rbxts/cmdr` ([documentation](https://eryn.io/Cmdr/), [types roblox-ts](https://github.com/sasial-dev/Cmdr#readme)) — commandes de développement.
|
- `@rbxts/cmdr` ([documentation](https://eryn.io/Cmdr/), [types roblox-ts](https://github.com/sasial-dev/Cmdr#readme)) — commandes de développement.
|
||||||
- `@rbxts/gamejoy` ([README](https://github.com/Rimuy/GameJoy#readme)) — contrôles de jeu.
|
- `@rbxts/gamejoy` ([README](https://github.com/Rimuy/GameJoy#readme)) — contrôles de jeu.
|
||||||
- `@rbxts/tween` ([README et API](https://github.com/Validark/Roblox-TS-Libraries/blob/master/tween/README.md)) — interpolations.
|
- `@rbxts/tween` ([README et API](https://github.com/Validark/Roblox-TS-Libraries/blob/master/tween/README.md)) — interpolations.
|
||||||
- `@rbxts/timer` ([README](https://github.com/Bytebit-Org/roblox-Timer#readme)) — temporisation.
|
- `@rbxts/timer` ([README](https://github.com/Bytebit-Org/roblox-Timer#readme)) — temporisation.
|
||||||
- `@rbxts/ui-labs` ([documentation](https://ui-labs.luau.page/docs/getstarted.html)) — prévisualisation d'interface.
|
- `@rbxts/ui-labs` ([documentation](https://ui-labs.luau.page/docs/getstarted.html)) — prévisualisation d'interface.
|
||||||
- `@rbxts/sound-manager` ([documentation du projet](https://github.com/xelnae-r/Sound-Manager#readme)) — gestion des sons
|
- `@rbxts/sound-manager` ([documentation du projet](https://github.com/xelnae-r/Sound-Manager#readme)) — gestion des sons
|
||||||
- `@rbxts/object-cache` ([documentation amont](https://github.com/Pyseph/ObjectCache#readme)) — cache de pièces/objets
|
- `@rbxts/object-cache` ([documentation amont](https://github.com/Pyseph/ObjectCache#readme)) — cache de pièces/objets
|
||||||
- `@rbxts/net` ([README](https://github.com/roblox-aurora/rbx-net#readme)) — contient `ExperienceBroadcastEvent` pour les messages entre serveurs.
|
- `@rbxts/net` ([README](https://github.com/roblox-aurora/rbx-net#readme)) — contient `ExperienceBroadcastEvent` pour les messages entre serveurs.
|
||||||
- `@rbxts-js/roblox-lua-promise` ([README](https://github.com/evaera/roblox-lua-promise#readme)) — implémentation de promesses
|
- `@rbxts-js/roblox-lua-promise` ([README](https://github.com/evaera/roblox-lua-promise#readme)) — implémentation de promesses
|
||||||
|
|
||||||
Pour choisir une approche lors d'une nouvelle fonctionnalité, le skill local [`roblox-feature-docs`](.agents/skills/roblox-feature-docs/SKILL.md) compare les bibliothèques pertinentes, leurs documentations et les API réellement disponibles dans les versions verrouillées.
|
Pour choisir une approche lors d'une nouvelle fonctionnalité, le skill local [`roblox-feature-docs`](.agents/skills/roblox-feature-docs/SKILL.md) compare les bibliothèques pertinentes, leurs documentations et les API réellement disponibles dans les versions verrouillées.
|
||||||
|
|
||||||
|
|
@ -79,6 +74,4 @@ Le serveur Blender peut exécuter du code Python dans Blender : utiliser uniquem
|
||||||
|
|
||||||
L'[API Cloud Creator Store](https://create.roblox.com/docs/cloud/reference/features/creator-store) est une API Roblox distincte des serveurs MCP ci-dessus. Elle n'est pas activée ici : son usage nécessite une clé et un cas d'utilisation précis.
|
L'[API Cloud Creator Store](https://create.roblox.com/docs/cloud/reference/features/creator-store) est une API Roblox distincte des serveurs MCP ci-dessus. Elle n'est pas activée ici : son usage nécessite une clé et un cas d'utilisation précis.
|
||||||
|
|
||||||
## Vérification du template
|
<sub>Version du template : 1.0.0</sub>
|
||||||
|
|
||||||
Sur Windows, l'installation verrouillée, `pnpm check`, `pnpm format:check`, `pnpm build` et `pnpm start -- --no-watch` ont réussi. `pnpm build` produit `game.rbxl` et `pnpm start` a lancé Roblox Studio. `codex mcp list` reconnaît les deux serveurs. Un test de jeu dans Studio et un appel MCP complet restent à effectuer.
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import test from "node:test";
|
||||||
|
import { URL } from "node:url";
|
||||||
|
import { runInNewContext } from "node:vm";
|
||||||
|
import ts from "typescript";
|
||||||
|
|
||||||
|
// Run the actual, engine-independent permission policy, not a duplicate of it.
|
||||||
|
const source = readFileSync(
|
||||||
|
new URL("../src/server/config/cmdr-permissions.ts", import.meta.url),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
const compiled = ts.transpileModule(source, {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS },
|
||||||
|
}).outputText;
|
||||||
|
const policyModule = { exports: {} };
|
||||||
|
runInNewContext(compiled, {
|
||||||
|
exports: policyModule.exports,
|
||||||
|
module: policyModule,
|
||||||
|
});
|
||||||
|
const { isCmdrAllowed, CMDR_PERMISSIONS } = policyModule.exports;
|
||||||
|
|
||||||
|
test("published game denies ordinary players by default", () => {
|
||||||
|
assert.equal(isCmdrAllowed(42, false, 99), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Studio allows local testing without publishing or configuring an admin", () => {
|
||||||
|
assert.equal(isCmdrAllowed(-1, true, undefined), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("experience owner is allowed in a user-owned published game", () => {
|
||||||
|
assert.equal(isCmdrAllowed(99, false, 99), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("no user owner means no automatic permission in a group-owned game", () => {
|
||||||
|
assert.equal(isCmdrAllowed(99, false, undefined), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("only explicitly listed users get allowlist access", () => {
|
||||||
|
const permissions = {
|
||||||
|
allowInStudio: false,
|
||||||
|
allowExperienceOwner: false,
|
||||||
|
adminUserIds: new Set([42]),
|
||||||
|
};
|
||||||
|
assert.equal(isCmdrAllowed(42, false, undefined, permissions), true);
|
||||||
|
assert.equal(isCmdrAllowed(43, false, undefined, permissions), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Studio and owner exceptions can both be disabled", () => {
|
||||||
|
const permissions = {
|
||||||
|
allowInStudio: false,
|
||||||
|
allowExperienceOwner: false,
|
||||||
|
adminUserIds: new Set(),
|
||||||
|
};
|
||||||
|
assert.equal(isCmdrAllowed(99, true, 99, permissions), false);
|
||||||
|
assert.equal(isCmdrAllowed(99, false, 99, permissions), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("published default configuration contains no placeholder administrator", () => {
|
||||||
|
assert.equal(CMDR_PERMISSIONS.adminUserIds.size, 0);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
#Requires -Version 5.1
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$DryRun,
|
||||||
|
[switch]$SkipChecks
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
|
||||||
|
function Invoke-Tool {
|
||||||
|
param([string]$Name, [string[]]$Arguments)
|
||||||
|
Write-Host "> $Name $($Arguments -join ' ')"
|
||||||
|
if ($DryRun) { return }
|
||||||
|
& $Name @Arguments
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "$Name a echoue (code $LASTEXITCODE). Corriger l'erreur puis relancer le script."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-SessionPath {
|
||||||
|
$machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
||||||
|
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||||
|
$env:Path = "$machinePath;$userPath;$env:Path;$env:USERPROFILE\.rokit\bin"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-Package {
|
||||||
|
param([string]$Id)
|
||||||
|
if (-not $DryRun) {
|
||||||
|
& winget list --id $Id --exact --source winget --accept-source-agreements --disable-interactivity
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Host "$Id est deja installe."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
# APP_NOT_INSTALLED: other errors must not be mistaken for a missing package.
|
||||||
|
if ($LASTEXITCODE -ne -1978335212) {
|
||||||
|
throw "Impossible de verifier $Id avec winget (code $LASTEXITCODE)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Invoke-Tool 'winget' @('install', '--id', $Id, '--exact', '--source', 'winget', '--silent', '--accept-package-agreements', '--accept-source-agreements', '--disable-interactivity')
|
||||||
|
if (-not $DryRun) { Update-SessionPath }
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location $projectRoot
|
||||||
|
try {
|
||||||
|
if (-not $DryRun -and -not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
||||||
|
throw "Installer App Installer (winget) depuis le Microsoft Store, puis relancer ce script."
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($package in @('Git.Git', 'jdx.mise', 'Rojo.Rokit', 'Roblox.RobloxStudio', 'BlenderFoundation.Blender')) {
|
||||||
|
Install-Package $package
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-Tool 'mise' @('trust', 'mise.toml')
|
||||||
|
Invoke-Tool 'mise' @('install')
|
||||||
|
Invoke-Tool 'rokit' @('install')
|
||||||
|
Invoke-Tool 'mise' @('exec', '--', 'pnpm', 'install', '--frozen-lockfile')
|
||||||
|
if (-not $SkipChecks) {
|
||||||
|
Invoke-Tool 'mise' @('exec', '--', 'pnpm', 'check')
|
||||||
|
Invoke-Tool 'mise' @('exec', '--', 'pnpm', 'build')
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($DryRun) {
|
||||||
|
Write-Host 'Apercu termine : aucune installation effectuee.'
|
||||||
|
} else {
|
||||||
|
Write-Host 'Installation terminee. Rouvrir le terminal et les clients MCP.'
|
||||||
|
Write-Host 'Pour les commandes pnpm/uvx, utiliser mise exec -- <commande> ou activer mise dans le shell.'
|
||||||
|
Write-Host 'Activer le MCP dans Studio et installer/activer l extension MCP officielle dans Blender (voir README).'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Controller, OnStart } from "@flamework/core";
|
||||||
|
import { CmdrClient } from "@rbxts/cmdr";
|
||||||
|
import { Players } from "@rbxts/services";
|
||||||
|
import { Trove } from "@rbxts/trove";
|
||||||
|
import { CMDR_ACCESS_ATTRIBUTE } from "shared/cmdr";
|
||||||
|
import { TEMPLATE_NAME } from "shared/template-name";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class CmdrController implements OnStart {
|
||||||
|
private readonly trove = new Trove();
|
||||||
|
|
||||||
|
public onStart(): void {
|
||||||
|
CmdrClient.SetActivationKeys([Enum.KeyCode.F2]);
|
||||||
|
CmdrClient.SetPlaceName(TEMPLATE_NAME);
|
||||||
|
CmdrClient.SetActivationUnlocksMouse(true);
|
||||||
|
this.trove.connect(Players.LocalPlayer.GetAttributeChangedSignal(CMDR_ACCESS_ATTRIBUTE), () =>
|
||||||
|
this.updateAccess(),
|
||||||
|
);
|
||||||
|
this.trove.attachToInstance(script);
|
||||||
|
this.updateAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateAccess(): void {
|
||||||
|
const enabled = Players.LocalPlayer.GetAttribute(CMDR_ACCESS_ATTRIBUTE) === true;
|
||||||
|
CmdrClient.SetEnabled(enabled);
|
||||||
|
if (!enabled) {
|
||||||
|
CmdrClient.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import { Flamework } from "@flamework/core";
|
import { Flamework } from "@flamework/core";
|
||||||
|
import "@flamework/components";
|
||||||
|
import Log from "@rbxts/log";
|
||||||
|
import { initializeLogging } from "shared/logging";
|
||||||
import { TEMPLATE_NAME } from "shared/template-name";
|
import { TEMPLATE_NAME } from "shared/template-name";
|
||||||
|
|
||||||
|
initializeLogging();
|
||||||
|
Flamework.addPaths("src/client/controllers");
|
||||||
Flamework.ignite();
|
Flamework.ignite();
|
||||||
print(`${TEMPLATE_NAME} ready (client)`);
|
Log.Info("{Template} ready (client)", TEMPLATE_NAME);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
export interface CmdrPermissions {
|
||||||
|
readonly allowInStudio: boolean;
|
||||||
|
readonly allowExperienceOwner: boolean;
|
||||||
|
readonly adminUserIds: ReadonlySet<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CMDR_PERMISSIONS: CmdrPermissions = {
|
||||||
|
allowInStudio: true,
|
||||||
|
allowExperienceOwner: true,
|
||||||
|
// Add individual Roblox UserIds here, especially for group-owned experiences.
|
||||||
|
adminUserIds: new Set<number>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isCmdrAllowed(
|
||||||
|
userId: number,
|
||||||
|
isStudio: boolean,
|
||||||
|
experienceOwnerUserId: number | undefined,
|
||||||
|
permissions: CmdrPermissions = CMDR_PERMISSIONS,
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
(isStudio && permissions.allowInStudio) ||
|
||||||
|
permissions.adminUserIds.has(userId) ||
|
||||||
|
(permissions.allowExperienceOwner &&
|
||||||
|
experienceOwnerUserId !== undefined &&
|
||||||
|
userId === experienceOwnerUserId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { Flamework } from "@flamework/core";
|
import { Flamework } from "@flamework/core";
|
||||||
|
import "@flamework/components";
|
||||||
|
import Log from "@rbxts/log";
|
||||||
|
import { initializeLogging } from "shared/logging";
|
||||||
import { TEMPLATE_NAME } from "shared/template-name";
|
import { TEMPLATE_NAME } from "shared/template-name";
|
||||||
|
|
||||||
|
initializeLogging();
|
||||||
Flamework.addPaths("src/server/services");
|
Flamework.addPaths("src/server/services");
|
||||||
Flamework.ignite();
|
Flamework.ignite();
|
||||||
print(`${TEMPLATE_NAME} ready (server)`);
|
Log.Info("{Template} ready (server)", TEMPLATE_NAME);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { OnStart, Service } from "@flamework/core";
|
||||||
|
import { Cmdr } from "@rbxts/cmdr";
|
||||||
|
import Log from "@rbxts/log";
|
||||||
|
import { Players, RunService } from "@rbxts/services";
|
||||||
|
import { Trove } from "@rbxts/trove";
|
||||||
|
import { isCmdrAllowed } from "server/config/cmdr-permissions";
|
||||||
|
import { CMDR_ACCESS_ATTRIBUTE } from "shared/cmdr";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CmdrService implements OnStart {
|
||||||
|
private readonly trove = new Trove();
|
||||||
|
|
||||||
|
public onStart(): void {
|
||||||
|
// Register authorization before exposing the built-in commands.
|
||||||
|
Cmdr.Registry.RegisterHook("BeforeRun", (context) => {
|
||||||
|
if (!this.canUseConsole(context.Executor)) {
|
||||||
|
return "You do not have permission to run this command.";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Cmdr.Registry.RegisterDefaultCommands();
|
||||||
|
|
||||||
|
this.trove.connect(Players.PlayerAdded, (player) => {
|
||||||
|
this.updateAccess(player);
|
||||||
|
});
|
||||||
|
for (const player of Players.GetPlayers()) {
|
||||||
|
this.updateAccess(player);
|
||||||
|
}
|
||||||
|
game.BindToClose(() => this.trove.destroy());
|
||||||
|
Log.Info("Cmdr ready with {CommandCount} commands", Cmdr.Registry.GetCommands().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private canUseConsole(player: Player): boolean {
|
||||||
|
return isCmdrAllowed(
|
||||||
|
player.UserId,
|
||||||
|
RunService.IsStudio(),
|
||||||
|
game.CreatorType === Enum.CreatorType.User ? game.CreatorId : undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateAccess(player: Player): void {
|
||||||
|
player.SetAttribute(CMDR_ACCESS_ATTRIBUTE, this.canUseConsole(player));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { OnStart, Service } from "@flamework/core";
|
import { OnStart, Service } from "@flamework/core";
|
||||||
|
import Log from "@rbxts/log";
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class StartupService implements OnStart {
|
export class StartupService implements OnStart {
|
||||||
public onStart(): void {
|
public onStart(): void {
|
||||||
print("CrownStudio template ready (server)");
|
Log.Debug("StartupService started");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
// This attribute controls visibility only. Authorization is checked on the server.
|
||||||
|
export const CMDR_ACCESS_ATTRIBUTE = "CmdrEnabled";
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Log, { LogLevel } from "@rbxts/log";
|
||||||
|
import { RunService } from "@rbxts/services";
|
||||||
|
|
||||||
|
export function initializeLogging(): void {
|
||||||
|
Log.SetLogger(
|
||||||
|
Log.Configure()
|
||||||
|
.SetMinLogLevel(RunService.IsStudio() ? LogLevel.Debugging : LogLevel.Information)
|
||||||
|
.EnrichWithProperty("Side", RunService.IsServer() ? "server" : "client")
|
||||||
|
.WriteTo(Log.RobloxOutput())
|
||||||
|
.Create(),
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue